* [PATCH v1 1/2] firmware: stratix10-svc: add FCS crypto-service commands for Agilex 5
2026-07-01 7:39 [PATCH v1 0/2] Add Altera SoCFPGA Crypto Service (FCS) driver hang.suan.wang
@ 2026-07-01 7:39 ` hang.suan.wang
2026-07-01 7:39 ` [PATCH v1 2/2] firmware: socfpga-fcs: add Altera SoCFPGA FCS driver with SDOS hang.suan.wang
1 sibling, 0 replies; 3+ messages in thread
From: hang.suan.wang @ 2026-07-01 7:39 UTC (permalink / raw)
To: Dinh Nguyen, linux-kernel, Michael S . Tsirkin, Huacai Chen,
Florian Fainelli, Chen-Yu Tsai
Cc: muhammad.nazim.amirul.nazle.asmade, tze.yee.ng, chee.nouk.phoon,
genevieve.chan
From: Hang Suan Wang <hang.suan.wang@altera.com>
The Agilex 5 Secure Device Manager (SDM 1.5) exposes an FPGA Crypto
Service (FCS) over the existing SIP SMC mailbox: a session-based
interface for crypto primitives such as SDOS (Secure Data Object
Service) encrypt/decrypt. The service layer has no command to drive it
yet.
Teach stratix10-svc about this interface so an in-kernel FCS client can
use it:
- add the client command codes COMMAND_FCS_CRYPTO_OPEN_SESSION,
COMMAND_FCS_CRYPTO_CLOSE_SESSION and COMMAND_FCS_SDOS_DATA_EXT (all
asynchronous), and grow stratix10_svc_client_msg::arg[] from three
to four entries so the SDOS command can carry its session, context,
mode and owner arguments;
- add the matching asynchronous SIP SMC function IDs
(INTEL_SIP_SMC_ASYNC_FCS_OPEN_CS_SESSION,
INTEL_SIP_SMC_ASYNC_FCS_CLOSE_CS_SESSION and
INTEL_SIP_SMC_ASYNC_FCS_CRYPTION_EXT) with their register-usage
documentation;
- match "intel,agilex5-svc" and register a "stratix10-fcs" child
platform device, mirroring the existing RSU child, so an FCS client
driver can bind without a dedicated device-tree node;
- dispatch the new commands in the asynchronous send and response
paths; for the SDOS data command, translate the source and
destination buffers (allocated from the service-layer gen_pool) to
physical addresses and pass them, together with the session/context
IDs and owner ID, to the SDM.
The transport is unchanged: Agilex 5 reuses the SIP SMC calling
convention and async mailbox ABI the driver already implements, so no
new transport mechanism is required.
The SDOS SMMU-remapped address slots currently carry the buffer
physical addresses; SMMU remapping support is added in a follow-up
series.
This is a prerequisite for the SoCFPGA FCS driver, the first in-tree
consumer of these commands.
Signed-off-by: Hang Suan Wang <hang.suan.wang@altera.com>
---
drivers/firmware/stratix10-svc.c | 56 ++++++++++++++--
include/linux/firmware/intel/stratix10-smc.h | 64 +++++++++++++++++++
.../firmware/intel/stratix10-svc-client.h | 18 +++++-
3 files changed, 133 insertions(+), 5 deletions(-)
diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c
index c24ca5823078..30035cb22b67 100644
--- a/drivers/firmware/stratix10-svc.c
+++ b/drivers/firmware/stratix10-svc.c
@@ -45,6 +45,7 @@
/* stratix10 service layer clients */
#define STRATIX10_RSU "stratix10-rsu"
+#define STRATIX10_FCS "stratix10-fcs"
/* Maximum number of SDM client IDs. */
#define MAX_SDM_CLIENT_IDS 16
@@ -104,9 +105,11 @@ struct stratix10_svc_chan;
/**
* struct stratix10_svc - svc private data
* @stratix10_svc_rsu: pointer to stratix10 RSU device
+ * @stratix10_svc_fcs: pointer to stratix10 FCS device
*/
struct stratix10_svc {
struct platform_device *stratix10_svc_rsu;
+ struct platform_device *stratix10_svc_fcs;
};
/**
@@ -1319,6 +1322,30 @@ int stratix10_svc_async_send(struct stratix10_svc_chan *chan, void *msg,
STRATIX10_SIP_SMC_SET_TRANSACTIONID_X1(handle->transaction_id);
switch (p_msg->command) {
+ case COMMAND_FCS_CRYPTO_OPEN_SESSION:
+ args.a0 = INTEL_SIP_SMC_ASYNC_FCS_OPEN_CS_SESSION;
+ break;
+ case COMMAND_FCS_CRYPTO_CLOSE_SESSION:
+ args.a0 = INTEL_SIP_SMC_ASYNC_FCS_CLOSE_CS_SESSION;
+ args.a2 = p_msg->arg[0];
+ break;
+ case COMMAND_FCS_SDOS_DATA_EXT:
+ args.a0 = INTEL_SIP_SMC_ASYNC_FCS_CRYPTION_EXT;
+ args.a2 = p_msg->arg[0];
+ args.a3 = p_msg->arg[1];
+ args.a4 = p_msg->arg[2];
+ /* payloads are allocated from the svc gen_pool; pass phys addr */
+ args.a5 = gen_pool_virt_to_phys(ctrl->genpool,
+ (unsigned long)p_msg->payload);
+ args.a6 = p_msg->payload_length;
+ args.a7 = gen_pool_virt_to_phys(ctrl->genpool,
+ (unsigned long)p_msg->payload_output);
+ args.a8 = p_msg->payload_length_output;
+ args.a9 = p_msg->arg[3];
+ /* SMMU remapping is added later; pass phys addr for now */
+ args.a10 = args.a5;
+ args.a11 = args.a7;
+ break;
case COMMAND_RSU_GET_SPT_TABLE:
args.a0 = INTEL_SIP_SMC_ASYNC_RSU_GET_SPT;
break;
@@ -1408,6 +1435,9 @@ static int stratix10_svc_async_prepare_response(struct stratix10_svc_chan *chan,
data->status = STRATIX10_GET_SDM_STATUS_CODE(handle->res.a1);
switch (p_msg->command) {
+ case COMMAND_FCS_CRYPTO_OPEN_SESSION:
+ case COMMAND_FCS_CRYPTO_CLOSE_SESSION:
+ case COMMAND_FCS_SDOS_DATA_EXT:
case COMMAND_RSU_NOTIFY:
break;
case COMMAND_RSU_GET_SPT_TABLE:
@@ -1908,6 +1938,7 @@ EXPORT_SYMBOL_GPL(stratix10_svc_free_memory);
static const struct of_device_id stratix10_svc_drv_match[] = {
{.compatible = "intel,stratix10-svc"},
{.compatible = "intel,agilex-svc"},
+ {.compatible = "intel,agilex5-svc"},
{},
};
@@ -2011,20 +2042,36 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev)
ret = platform_device_add(svc->stratix10_svc_rsu);
if (ret)
- goto err_put_device;
+ goto err_put_rsu;
+
+ svc->stratix10_svc_fcs = platform_device_alloc(STRATIX10_FCS, 0);
+ if (!svc->stratix10_svc_fcs) {
+ dev_err(dev, "failed to allocate %s device\n", STRATIX10_FCS);
+ ret = -ENOMEM;
+ goto err_unregister_rsu;
+ }
+
+ ret = platform_device_add(svc->stratix10_svc_fcs);
+ if (ret)
+ goto err_put_fcs;
ret = of_platform_default_populate(dev_of_node(dev), NULL, dev);
if (ret)
- goto err_unregister_rsu_dev;
+ goto err_unregister_fcs;
pr_info("Intel Service Layer Driver Initialized\n");
return 0;
-err_unregister_rsu_dev:
+err_unregister_fcs:
+ platform_device_unregister(svc->stratix10_svc_fcs);
+ goto err_unregister_rsu;
+err_put_fcs:
+ platform_device_put(svc->stratix10_svc_fcs);
+err_unregister_rsu:
platform_device_unregister(svc->stratix10_svc_rsu);
goto err_free_fifos;
-err_put_device:
+err_put_rsu:
platform_device_put(svc->stratix10_svc_rsu);
err_free_fifos:
/* only remove from list if list_add_tail() was reached */
@@ -2051,6 +2098,7 @@ static void stratix10_svc_drv_remove(struct platform_device *pdev)
of_platform_depopulate(ctrl->dev);
platform_device_unregister(svc->stratix10_svc_rsu);
+ platform_device_unregister(svc->stratix10_svc_fcs);
for (i = 0; i < SVC_NUM_CHANNEL; i++) {
if (ctrl->chans[i].task) {
diff --git a/include/linux/firmware/intel/stratix10-smc.h b/include/linux/firmware/intel/stratix10-smc.h
index 9116512169dc..d7ad6bd6f669 100644
--- a/include/linux/firmware/intel/stratix10-smc.h
+++ b/include/linux/firmware/intel/stratix10-smc.h
@@ -640,6 +640,70 @@ INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE)
#define INTEL_SIP_SMC_FCS_GET_PROVISION_DATA \
INTEL_SIP_SMC_STD_CALL_VAL(INTEL_SIP_SMC_FUNCID_FCS_GET_PROVISION_DATA)
+/**
+ * Request INTEL_SIP_SMC_ASYNC_FCS_CRYPTION_EXT
+ * Async call to perform encryption/decryption
+ *
+ * Call register usage:
+ * a0 INTEL_SIP_SMC_ASYNC_FCS_CRYPTION_EXT
+ * a1 transaction job id
+ * a2 session ID
+ * a3 context ID
+ * a4 cryption operating mode (1 for encryption and 0 for decryption)
+ * a5 physical address of source
+ * a6 size of source
+ * a7 physical address of destination
+ * a8 size of destination
+ * a9 sdos ownership
+ * a10 smmu remapped address of source
+ * a11 smmu remapped address of destination
+ * a12-a17 not used
+ *
+ * Return status:
+ * a0 INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_STATUS_ERROR
+ * a1-a17 not used
+ */
+#define INTEL_SIP_SMC_ASYNC_FUNC_ID_FCS_CRYPTION_EXT (0x12F)
+#define INTEL_SIP_SMC_ASYNC_FCS_CRYPTION_EXT \
+ INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_FCS_CRYPTION_EXT)
+
+/**
+ * Request INTEL_SIP_SMC_ASYNC_FCS_OPEN_CS_SESSION
+ * Async call to open and establish a crypto service session with firmware
+ *
+ * Call register usage:
+ * a0 INTEL_SIP_SMC_FCS_OPEN_CRYPTO_SERVICE_SESSION
+ * a1 transaction job id
+ * a2-a17 not used
+ *
+ * Return status:
+ * a0 INTEL_SIP_SMC_STATUS_OK ,INTEL_SIP_SMC_STATUS_REJECTED
+ * or INTEL_SIP_SMC_STATUS_BUSY
+ * a1-a17 not used
+ */
+#define INTEL_SIP_SMC_ASYNC_FUNC_ID_FCS_OPEN_CS_SESSION (0x13A)
+#define INTEL_SIP_SMC_ASYNC_FCS_OPEN_CS_SESSION \
+ INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_FCS_OPEN_CS_SESSION)
+
+/**
+ * Request INTEL_SIP_SMC_ASYNC_FCS_CLOSE_CS_SESSION
+ * Async call to close a service session
+ *
+ * Call register usage:
+ * a0 INTEL_SIP_SMC_ASYNC_FCS_CLOSE_CS_SESSION
+ * a1 transaction job id
+ * a2 session ID
+ * a3-a17 not used
+ *
+ * Return status:
+ * a0 INTEL_SIP_SMC_STATUS_OK ,INTEL_SIP_SMC_STATUS_REJECTED
+ * or INTEL_SIP_SMC_STATUS_BUSY
+ * a1-a17 not used
+ */
+#define INTEL_SIP_SMC_ASYNC_FUNC_ID_FCS_CLOSE_CS_SESSION (0x13B)
+#define INTEL_SIP_SMC_ASYNC_FCS_CLOSE_CS_SESSION \
+ INTEL_SIP_SMC_ASYNC_VAL(INTEL_SIP_SMC_ASYNC_FUNC_ID_FCS_CLOSE_CS_SESSION)
+
/**
* Request INTEL_SIP_SMC_HWMON_READTEMP
* Sync call to request temperature
diff --git a/include/linux/firmware/intel/stratix10-svc-client.h b/include/linux/firmware/intel/stratix10-svc-client.h
index 3edd93502bf8..285fddafeacb 100644
--- a/include/linux/firmware/intel/stratix10-svc-client.h
+++ b/include/linux/firmware/intel/stratix10-svc-client.h
@@ -7,6 +7,8 @@
#ifndef __STRATIX10_SVC_CLIENT_H
#define __STRATIX10_SVC_CLIENT_H
+#include <linux/types.h>
+
/*
* Service layer driver supports client names
*
@@ -122,6 +124,15 @@ struct stratix10_svc_chan;
* @COMMAND_SMC_SVC_VERSION: Non-mailbox SMC SVC API Version,
* return status is SVC_STATUS_OK
*
+ * @COMMAND_FCS_CRYPTO_OPEN_SESSION: open the crypto service session(s),
+ * return status is SVC_STATUS_OK or SVC_STATUS_ERROR
+ *
+ * @COMMAND_FCS_CRYPTO_CLOSE_SESSION: close the crypto service session(s),
+ * return status is SVC_STATUS_OK or SVC_STATUS_ERROR
+ *
+ * @COMMAND_FCS_SDOS_DATA_EXT: extend SDOS data encryption & decryption,
+ * return status is SVC_STATUS_OK or SVC_STATUS_ERROR
+ *
* @COMMAND_MBOX_SEND_CMD: send generic mailbox command, return status is
* SVC_STATUS_OK or SVC_STATUS_ERROR
*
@@ -185,6 +196,11 @@ enum stratix10_svc_command_code {
COMMAND_FCS_RANDOM_NUMBER_GEN,
/* for general status poll */
COMMAND_POLL_SERVICE_STATUS = 40,
+ /* for crypto service */
+ COMMAND_FCS_CRYPTO_OPEN_SESSION = 50,
+ COMMAND_FCS_CRYPTO_CLOSE_SESSION,
+ /* for extended SDOS encrypt/decrypt */
+ COMMAND_FCS_SDOS_DATA_EXT = 82,
/* for generic mailbox send command */
COMMAND_MBOX_SEND_CMD = 100,
/* Non-mailbox SMC Call */
@@ -210,7 +226,7 @@ struct stratix10_svc_client_msg {
void *payload_output;
size_t payload_length_output;
enum stratix10_svc_command_code command;
- u64 arg[3];
+ u64 arg[4];
};
/**
--
2.43.7
^ permalink raw reply related [flat|nested] 3+ messages in thread* [PATCH v1 2/2] firmware: socfpga-fcs: add Altera SoCFPGA FCS driver with SDOS
2026-07-01 7:39 [PATCH v1 0/2] Add Altera SoCFPGA Crypto Service (FCS) driver hang.suan.wang
2026-07-01 7:39 ` [PATCH v1 1/2] firmware: stratix10-svc: add FCS crypto-service commands for Agilex 5 hang.suan.wang
@ 2026-07-01 7:39 ` hang.suan.wang
1 sibling, 0 replies; 3+ messages in thread
From: hang.suan.wang @ 2026-07-01 7:39 UTC (permalink / raw)
To: Dinh Nguyen, linux-kernel, Michael S . Tsirkin, Huacai Chen,
Florian Fainelli, Chen-Yu Tsai
Cc: muhammad.nazim.amirul.nazle.asmade, tze.yee.ng, chee.nouk.phoon,
genevieve.chan
From: Hang Suan Wang <hang.suan.wang@altera.com>
Add the Altera SoCFPGA Crypto Service (FCS) driver, which exposes the
Secure Data Object Service (SDOS) encrypt/decrypt operation and its crypto
session lifecycle to non-secure host software.
The SDOS is the FCS feature that protects data at rest: the SDM encrypts
and decrypts using a key derived from a device-unique SDOS root key plus an
SDM-generated IV, so the host never handles raw key material or IVs. It
only submits plaintext it already owns and receives authenticated
ciphertext objects managed by the SDM. A primary use case is black key
provisioning, where operational keys are installed without ever appearing
in cleartext.
The driver is a standalone module and describes no hardware of its own.
It binds by name to the "stratix10-fcs" platform device that the
stratix10-svc driver registers in code, so no device-tree node is needed,
and detects the SoC by matching the service-layer compatible. It exposes
the following sysfs attributes. The SDOS requests are issued to the SDM
through the stratix10-svc asynchronous SIP SMC. Source and destination
buffers are taken from the service-layer memory pool so the SDM can reach
them via physical or SMMU-remapped addresses.
For encryption the SDM returns a structured object (metadata, IV, HMAC,
ciphertext); for decryption it validates the HMAC, recovers the parameters
from the object header, and enforces a 64-bit owner ID so that only the
creator of an object can decrypt it.
Signed-off-by: Hang Suan Wang <hang.suan.wang@altera.com>
---
MAINTAINERS | 8 +
drivers/firmware/Kconfig | 16 +
drivers/firmware/Makefile | 2 +
drivers/firmware/socfpga-fcs-core.c | 589 +++++++++++++++++++++
drivers/firmware/socfpga-fcs.c | 294 ++++++++++
include/linux/firmware/intel/socfpga-fcs.h | 134 +++++
6 files changed, 1043 insertions(+)
create mode 100644 drivers/firmware/socfpga-fcs-core.c
create mode 100644 drivers/firmware/socfpga-fcs.c
create mode 100644 include/linux/firmware/intel/socfpga-fcs.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..72b12b32f8fe 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -946,6 +946,14 @@ ALPS PS/2 TOUCHPAD DRIVER
R: Pali Rohár <pali@kernel.org>
F: drivers/input/mouse/alps.*
+ALTERA FCS DRIVER
+M: Hang Suan Wang <hang.suan.wang@altera.com>
+M: Genevieve Chan <genevieve.chan@altera.com>
+L: linux-arm-kernel@lists.infradead.org
+S: Maintained
+F: drivers/firmware/socfpga-fcs*
+F: include/linux/firmware/intel/socfpga-fcs*
+
ALTERA MAILBOX DRIVER
M: Tien Sung Ang <tiensung.ang@altera.com>
S: Maintained
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 12dc70254842..9a70def6932a 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -172,6 +172,22 @@ config INTEL_STRATIX10_RSU
Say Y here if you want Intel RSU support.
+config ALTERA_SOCFPGA_FCS
+ tristate "Altera SoCFPGA Crypto Service (FCS) configuration"
+ depends on INTEL_STRATIX10_SERVICE
+ default n
+ help
+ Altera SoCFPGA Crypto Service (FCS) driver exposes interfaces access
+ through the Intel Service Layer to user space via sysfs device
+ attribute nodes. It exposes the crypto and key-management services
+ of the Secure Device Manager (SDM) to the host software stack and
+ requests are forwarded to Arm Trusted Firmware. The SDM then
+ executes or authorizes them using device-rooted security resources.
+ Protected key material remains within the secure firmware boundary
+ and is not directly exposed to non-secure host software.
+
+ Say Y here if you want Altera SoCFPGA FCS support.
+
config MTK_ADSP_IPC
tristate "MTK ADSP IPC Protocol driver"
depends on MTK_ADSP_MBOX
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 4ddec2820c96..e9f52f0e5f7a 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -10,6 +10,8 @@ obj-$(CONFIG_EDD) += edd.o
obj-$(CONFIG_DMIID) += dmi-id.o
obj-$(CONFIG_INTEL_STRATIX10_SERVICE) += stratix10-svc.o
obj-$(CONFIG_INTEL_STRATIX10_RSU) += stratix10-rsu.o
+obj-$(CONFIG_ALTERA_SOCFPGA_FCS) += altera-fcs.o
+altera-fcs-y := socfpga-fcs.o socfpga-fcs-core.o
obj-$(CONFIG_ISCSI_IBFT_FIND) += iscsi_ibft_find.o
obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o
obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o
diff --git a/drivers/firmware/socfpga-fcs-core.c b/drivers/firmware/socfpga-fcs-core.c
new file mode 100644
index 000000000000..62460fd0e9e4
--- /dev/null
+++ b/drivers/firmware/socfpga-fcs-core.c
@@ -0,0 +1,589 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 Altera Corporation
+ */
+
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/firmware/intel/socfpga-fcs.h>
+#include <linux/firmware/intel/stratix10-svc-client.h>
+
+#define OWNER_ID_OFFSET 12
+#define OWNER_ID_SIZE 8
+
+#define SDOS_DECRYPTION_REPROVISION_KEY_WARN 0x102
+#define SDOS_DECRYPTION_NOT_LATEST_KEY_WARN 0x103
+
+#define MSG_RETRY 3
+#define RETRY_SLEEP_MS 1
+#define TIMEOUT 1000
+
+static struct socfpga_fcs_priv *priv;
+
+/**
+ * fcs_atf_version_callback() - service-layer callback for the ATF version query
+ * @client: pointer to the stratix10-svc client
+ * @data: pointer to the service-layer callback data
+ *
+ * Store the returned Arm Trusted Firmware version (or mailbox error) in @priv
+ * and signal completion to the waiting caller.
+ */
+static void fcs_atf_version_callback(struct stratix10_svc_client *client,
+ struct stratix10_svc_cb_data *data)
+{
+ struct socfpga_fcs_priv *p = client->priv;
+
+ p->status = data->status;
+ if (data->status == BIT(SVC_STATUS_OK)) {
+ p->status = 0;
+ p->atf_version[0] = *((unsigned int *)data->kaddr1);
+ p->atf_version[1] = *((unsigned int *)data->kaddr2);
+ p->atf_version[2] = *((unsigned int *)data->kaddr3);
+ } else if (data->status == BIT(SVC_STATUS_ERROR)) {
+ p->status = *((unsigned int *)data->kaddr1);
+ dev_err(client->dev, "mbox_error=0x%x\n", p->status);
+ }
+
+ complete(&p->completion);
+}
+
+/**
+ * fcs_async_callback() - completion callback for an async service request
+ * @ptr: pointer to the completion to signal
+ */
+static void fcs_async_callback(void *ptr)
+{
+ if (ptr)
+ complete(ptr);
+}
+
+/**
+ * fcs_svc_send_request() - build and send an FCS command to the service layer
+ * @command: FCS command code to dispatch
+ * @timeout: time to wait for completion, in jiffies
+ *
+ * Build the service-layer message for @command and send it through the
+ * stratix10-svc service driver, using the synchronous path for the ATF version
+ * query and the asynchronous mailbox path (with retries) for the remaining
+ * commands.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+static int fcs_svc_send_request(enum fcs_command_code command,
+ unsigned long timeout)
+{
+ struct fcs_cmd_context *k_ctx = &priv->k_ctx;
+ struct stratix10_svc_cb_data data;
+ struct completion completion;
+ void *handle = NULL;
+ int status, index;
+ int ret = 0;
+ struct stratix10_svc_client_msg *msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+
+ priv->status = 0;
+ priv->resp = 0;
+
+ switch (command) {
+ case FCS_DEV_CRYPTO_OPEN_SESSION:
+ pr_debug("Sending command: COMMAND_FCS_CRYPTO_OPEN_SESSION\n");
+ msg->command = COMMAND_FCS_CRYPTO_OPEN_SESSION;
+ break;
+
+ case FCS_DEV_CRYPTO_CLOSE_SESSION:
+ pr_debug("Sending command: COMMAND_FCS_CRYPTO_CLOSE_SESSION with session_id: 0x%x\n",
+ priv->session_id);
+ msg->arg[0] = priv->session_id;
+ msg->command = COMMAND_FCS_CRYPTO_CLOSE_SESSION;
+ break;
+
+ case FCS_DEV_ATF_VERSION:
+ pr_debug("Sending command: COMMAND_SMC_ATF_BUILD_VER\n");
+ msg->command = COMMAND_SMC_ATF_BUILD_VER;
+ priv->client.receive_cb = fcs_atf_version_callback;
+ break;
+
+ case FCS_DEV_SDOS_DATA_EXT:
+ pr_debug("Sending command: COMMAND_FCS_SDOS_DATA_EXT with session_id: 0x%x, context_id: 0x%x, op_mode: 0x%x, own: 0x%llx\n",
+ priv->session_id, k_ctx->sdos.context_id,
+ k_ctx->sdos.op_mode, k_ctx->sdos.own);
+ msg->arg[0] = priv->session_id;
+ msg->arg[1] = k_ctx->sdos.context_id;
+ msg->arg[2] = k_ctx->sdos.op_mode;
+ msg->arg[3] = k_ctx->sdos.own;
+ msg->payload = k_ctx->sdos.src;
+ msg->payload_length = k_ctx->sdos.src_size;
+ msg->payload_output = k_ctx->sdos.dst;
+ msg->payload_length_output = *k_ctx->sdos.dst_size;
+ msg->command = COMMAND_FCS_SDOS_DATA_EXT;
+ break;
+
+ default:
+ pr_err("Unknown command: 0x%x\n", command);
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret) {
+ kfree(msg);
+ return ret;
+ }
+
+ if (command == FCS_DEV_ATF_VERSION) {
+ reinit_completion(&priv->completion);
+
+ ret = stratix10_svc_send(priv->chan, msg);
+ if (ret) {
+ pr_err("failed to send message to service channel\n");
+ goto fun_ret;
+ }
+
+ if (!wait_for_completion_timeout(&priv->completion, timeout)) {
+ pr_err("svc timeout to get completed status\n");
+ ret = -ETIMEDOUT;
+ }
+fun_ret:
+ kfree(msg);
+ return ret;
+ }
+
+ init_completion(&completion);
+
+ for (index = 0; index < MSG_RETRY; index++) {
+ status = stratix10_svc_async_send(priv->chan, msg, &handle,
+ fcs_async_callback,
+ &completion);
+ if (status == 0)
+ break;
+ msleep(RETRY_SLEEP_MS);
+ }
+
+ if (!handle || status != 0) {
+ pr_err("Failed to send async message\n");
+ kfree(msg);
+ return -ETIMEDOUT;
+ }
+
+ ret = wait_for_completion_io_timeout(&completion, (TIMEOUT));
+ if (ret > 0)
+ pr_debug("Received async interrupt\n");
+ else
+ pr_err("timeout occurred while waiting for async message\n");
+
+ ret = stratix10_svc_async_poll(priv->chan, handle, &data);
+ if (ret) {
+ pr_err("Failed to poll async message\n");
+ goto out;
+ }
+
+ priv->status = data.status;
+
+ if (data.kaddr1)
+ priv->resp = *((u32 *)data.kaddr1);
+ else
+ priv->resp = 0;
+
+out:
+ stratix10_svc_async_done(priv->chan, handle);
+ kfree(msg);
+
+ return ret;
+}
+
+/**
+ * fcs_session_open() - open an FCS crypto service session
+ * @k_ctx: pointer to the kernel-side FCS command context
+ *
+ * Request a new session from the SDM, generate the session UUID and copy it,
+ * together with the mailbox status, back to user space.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int fcs_session_open(struct fcs_cmd_context *const k_ctx)
+{
+ int ret = 0;
+
+ ret = fcs_svc_send_request(FCS_DEV_CRYPTO_OPEN_SESSION,
+ SVC_FCS_REQUEST_TIMEOUT_MS);
+ if (ret) {
+ pr_err("Failed to send the cmd=%d,ret=%d\n",
+ FCS_DEV_CRYPTO_OPEN_SESSION, ret);
+ return ret;
+ }
+
+ if (priv->status) {
+ ret = -EIO;
+ pr_err("Mailbox error, Failed to open session ret: %d\n", ret);
+ goto copy_mbox_status;
+ }
+
+ uuid_gen(&priv->uuid_id);
+
+ memcpy(&priv->session_id, &priv->resp, sizeof(priv->session_id));
+
+ ret = copy_to_user(k_ctx->open_session.suuid, &priv->uuid_id,
+ sizeof(uuid_t)) ? -EFAULT : 0;
+ if (ret) {
+ pr_err("Failed to copy session ID to user suuid addr: %p ret: %d\n",
+ k_ctx->open_session.suuid, ret);
+ goto copy_mbox_status;
+ }
+
+copy_mbox_status:
+ if (copy_to_user(k_ctx->error_code_addr, &priv->status,
+ sizeof(priv->status))) {
+ pr_err("Failed to copy mail box status code to user\n");
+ /* surface the copy failure only if nothing failed earlier */
+ if (!ret)
+ ret = -EFAULT;
+ }
+
+ return ret;
+}
+
+/**
+ * fcs_session_close() - close an FCS crypto service session
+ * @k_ctx: pointer to the kernel-side FCS command context
+ *
+ * Validate the caller-supplied session UUID, ask the SDM to close the session
+ * and copy the mailbox status back to user space.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int fcs_session_close(struct fcs_cmd_context *const k_ctx)
+{
+ int ret = 0;
+ struct fcs_cmd_context ctx;
+
+ memcpy(&ctx, k_ctx, sizeof(struct fcs_cmd_context));
+
+ if (!uuid_equal(&priv->uuid_id, &k_ctx->close_session.suuid)) {
+ ret = -EINVAL;
+ pr_err("Session UUID Mismatch ret: %d\n", ret);
+ return ret;
+ }
+
+ ret = fcs_svc_send_request(FCS_DEV_CRYPTO_CLOSE_SESSION,
+ SVC_FCS_REQUEST_TIMEOUT_MS);
+ if (ret) {
+ pr_err("Failed to send the cmd=%d,ret=%d\n",
+ FCS_DEV_CRYPTO_CLOSE_SESSION, ret);
+ return ret;
+ }
+
+ memset(&priv->uuid_id, 0, sizeof(uuid_t));
+ priv->session_id = 0;
+ if (priv->status) {
+ ret = -EIO;
+ pr_err("Mailbox error, Failed to close session ret: %d\n", ret);
+ }
+
+ if (copy_to_user(ctx.error_code_addr, &priv->status,
+ sizeof(priv->status))) {
+ pr_err("Failed to copy mail box status code to user\n");
+ /* surface the copy failure only if nothing failed earlier */
+ if (!ret)
+ ret = -EFAULT;
+ }
+
+ return ret;
+}
+
+/**
+ * fcs_get_atf_version() - return the cached Arm Trusted Firmware version
+ * @version: array of three u32 entries to receive the major, minor and patch
+ * version numbers
+ */
+void fcs_get_atf_version(u32 *version)
+{
+ memcpy(version, priv->atf_version, sizeof(priv->atf_version));
+}
+
+/**
+ * fcs_sdos_crypt() - perform an SDOS encrypt or decrypt operation
+ * @k_ctx: pointer to the kernel-side FCS command context
+ *
+ * Allocate service-layer source and destination buffers, copy the input from
+ * user space, drive the SDOS data command and copy the result and length back
+ * to user space. The operation direction is selected by @k_ctx->sdos.op_mode.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int fcs_sdos_crypt(struct fcs_cmd_context *const k_ctx)
+{
+ void *s_buf = NULL, *d_buf = NULL;
+ struct fcs_cmd_context ctx;
+ u32 output_size;
+ u32 dst_cap;
+ u64 owner_id;
+ int ret = 0;
+ char *temp;
+
+ memcpy(&ctx, k_ctx, sizeof(struct fcs_cmd_context));
+
+ if (!ctx.sdos.dst || !ctx.sdos.dst_size)
+ return -EINVAL;
+
+ /* Caller-provided output buffer capacity (in/out parameter) */
+ if (copy_from_user(&dst_cap, ctx.sdos.dst_size, sizeof(dst_cap)))
+ return -EFAULT;
+
+ if (ctx.sdos.op_mode) {
+ output_size = SDOS_ENCRYPTED_MAX_SZ;
+ /* encrypt: input is header + plaintext */
+ if (k_ctx->sdos.src_size < SDOS_DECRYPTED_MIN_SZ ||
+ k_ctx->sdos.src_size > SDOS_DECRYPTED_MAX_SZ) {
+ pr_err("Invalid SDOS src_size %u\n", k_ctx->sdos.src_size);
+ return -EINVAL;
+ }
+ } else {
+ output_size = SDOS_DECRYPTED_MAX_SZ;
+ /* decrypt: input is header + plaintext + HMAC */
+ if (k_ctx->sdos.src_size < SDOS_ENCRYPTED_MIN_SZ ||
+ k_ctx->sdos.src_size > SDOS_ENCRYPTED_MAX_SZ) {
+ pr_err("Invalid SDOS src_size %u\n", k_ctx->sdos.src_size);
+ return -EINVAL;
+ }
+ }
+
+ s_buf = stratix10_svc_allocate_memory(priv->chan, k_ctx->sdos.src_size);
+ if (IS_ERR(s_buf)) {
+ ret = -ENOMEM;
+ pr_err("Failed to allocate memory for SDOS input data kernel buffer ret: %d\n",
+ ret);
+ return ret;
+ }
+
+ k_ctx->sdos.dst_size = &output_size;
+
+ d_buf = stratix10_svc_allocate_memory(priv->chan, *k_ctx->sdos.dst_size);
+ if (IS_ERR(d_buf)) {
+ ret = -ENOMEM;
+ pr_err("Failed to allocate memory for SDOS output kernel buffer ret: %d\n", ret);
+ goto free_sbuf;
+ }
+
+ /* Copy the user space input data to the input data kernel buffer */
+ ret = copy_from_user(s_buf, k_ctx->sdos.src,
+ k_ctx->sdos.src_size) ? -EFAULT : 0;
+ if (ret) {
+ pr_err("Failed to copy SDOS data from user to kernel buffer ret: %d\n", ret);
+ goto free_dbuf;
+ }
+
+ /* Get Owner ID from buf */
+ temp = (uint8_t *)s_buf;
+ memcpy(&owner_id, temp + OWNER_ID_OFFSET, OWNER_ID_SIZE);
+ k_ctx->sdos.own = owner_id;
+ k_ctx->sdos.src = s_buf;
+ k_ctx->sdos.dst = d_buf;
+
+ ret = fcs_svc_send_request(FCS_DEV_SDOS_DATA_EXT,
+ SVC_FCS_REQUEST_TIMEOUT_MS);
+ if (ret) {
+ pr_err("Failed to send the cmd=%d,ret=%d\n", FCS_DEV_SDOS_DATA_EXT, ret);
+ goto free_dbuf;
+ }
+ if (priv->status &&
+ priv->status != SDOS_DECRYPTION_REPROVISION_KEY_WARN &&
+ priv->status != SDOS_DECRYPTION_NOT_LATEST_KEY_WARN) {
+ pr_err("Failed to perform SDOS operation ret: %d Mailbox Status = %d\n",
+ ret, priv->status);
+ goto copy_mbox_status;
+ }
+
+ /*
+ * priv->resp is reported by firmware; never trust it to read back
+ * more than the kernel output buffer (d_buf) actually holds,
+ * otherwise the copy below would leak adjacent kernel memory.
+ */
+ if (priv->resp > output_size) {
+ pr_err("SDOS output %u exceeds kernel buffer %u\n",
+ priv->resp, output_size);
+ ret = -EIO;
+ goto copy_mbox_status;
+ }
+
+ /* Do not write past the caller-provided output buffer */
+ if (priv->resp > dst_cap) {
+ pr_err("SDOS output %u exceeds caller buffer %u\n",
+ priv->resp, dst_cap);
+ ret = -EMSGSIZE;
+ goto copy_mbox_status;
+ }
+
+ /* Copy the encrypted/decrypted output from kernel space to user space */
+ ret = copy_to_user(ctx.sdos.dst, d_buf, priv->resp) ? -EFAULT : 0;
+ if (ret) {
+ pr_err("Failed to copy encrypted output to user ret: %d\n", ret);
+ goto copy_mbox_status;
+ }
+
+ /* Copy the encrypted output length from kernel space to user space */
+ ret = copy_to_user(ctx.sdos.dst_size, &priv->resp,
+ sizeof(priv->resp)) ? -EFAULT : 0;
+ if (ret)
+ pr_err("Failed to copy encrypted output length to user ret: %d\n", ret);
+
+copy_mbox_status:
+ if (copy_to_user(ctx.error_code_addr, &priv->status,
+ sizeof(priv->status))) {
+ pr_err("Failed to copy mailbox status code to user\n");
+ /* surface the copy failure only if nothing failed earlier */
+ if (!ret)
+ ret = -EFAULT;
+ }
+free_dbuf:
+ stratix10_svc_free_memory(priv->chan, d_buf);
+free_sbuf:
+ stratix10_svc_free_memory(priv->chan, s_buf);
+
+ return ret;
+}
+
+/**
+ * fcs_acquire_cmd_ctx() - take the FCS lock and return the command context
+ *
+ * Serialises access to the shared command context across concurrent callers.
+ * The caller must release it with fcs_release_cmd_ctx().
+ *
+ * Return: pointer to the locked FCS command context.
+ */
+struct fcs_cmd_context *fcs_acquire_cmd_ctx(void)
+{
+ mutex_lock(&priv->lock);
+ return &priv->k_ctx;
+}
+
+/**
+ * fcs_release_cmd_ctx() - release the FCS command context lock
+ * @k_ctx: pointer to the FCS command context previously acquired
+ */
+void fcs_release_cmd_ctx(struct fcs_cmd_context *const k_ctx)
+{
+ mutex_unlock(&priv->lock);
+}
+
+/**
+ * fcs_read_version_from_atf() - query the Arm Trusted Firmware build version
+ *
+ * Send the ATF version command to the SDM and cache the result in @priv.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+static int fcs_read_version_from_atf(void)
+{
+ int ret = 0;
+
+ ret = fcs_svc_send_request(FCS_DEV_ATF_VERSION,
+ SVC_FCS_REQUEST_TIMEOUT_MS);
+ if (ret) {
+ pr_err("Failed to send the cmd=%d,ret=%d\n", FCS_DEV_ATF_VERSION, ret);
+ return ret;
+ }
+
+ if (priv->status) {
+ ret = -EIO;
+ pr_err("Mailbox error, Failed to read ATF version ret: %d\n", ret);
+ }
+
+ stratix10_svc_done(priv->chan);
+
+ return ret;
+}
+
+/**
+ * fcs_init() - allocate and initialise the FCS private state
+ * @dev: pointer to fcs device
+ *
+ * Allocate @priv, request the service channel, register the async client,
+ * detect the platform and read the ATF version.
+ *
+ * Return: 0 on success, -EPROBE_DEFER or negative errno on failure.
+ */
+int fcs_init(struct device *dev)
+{
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(struct socfpga_fcs_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ mutex_init(&priv->lock);
+
+ priv->dev = dev;
+ priv->client.dev = dev;
+ priv->client.receive_cb = NULL;
+ priv->client.priv = priv;
+
+ priv->chan = stratix10_svc_request_channel_byname(&priv->client,
+ SVC_CLIENT_FCS);
+ if (IS_ERR(priv->chan)) {
+ pr_err("couldn't get service channel %s\n", SVC_CLIENT_FCS);
+ return -EPROBE_DEFER;
+ }
+
+ ret = stratix10_svc_add_async_client(priv->chan, true);
+ if (ret) {
+ pr_err("Failed to add async client\n");
+ goto free_chan;
+ }
+
+ init_completion(&priv->completion);
+
+ fcs_read_version_from_atf();
+
+ return 0;
+
+remove_async:
+ stratix10_svc_remove_async_client(priv->chan);
+free_chan:
+ stratix10_svc_free_channel(priv->chan);
+
+ return ret;
+}
+
+/**
+ * fcs_deinit() - tear down the FCS private state
+ *
+ * Close any open session, remove the async client, free the service channel
+ * and clear @priv.
+ */
+void fcs_deinit(void)
+{
+ if (priv && priv->session_id) {
+ int ret = fcs_svc_send_request(FCS_DEV_CRYPTO_CLOSE_SESSION,
+ SVC_FCS_REQUEST_TIMEOUT_MS);
+ if (ret)
+ pr_err("Failed to close FCS service session,ret=%d\n", ret);
+ }
+
+ if (priv) {
+ stratix10_svc_remove_async_client(priv->chan);
+ stratix10_svc_free_channel(priv->chan);
+ }
+
+ priv = NULL;
+}
+
+/**
+ * fcs_get_platform() - return the detected FCS platform identifier
+ *
+ * Return: the platform identifier cached in @priv.
+ */
+int fcs_get_platform(void)
+{
+ return priv->platform;
+}
+
+/**
+ * fcs_cleanup() - release the FCS service channel and clear the state
+ */
+void fcs_cleanup(void)
+{
+ if (priv)
+ stratix10_svc_free_channel(priv->chan);
+
+ priv = NULL;
+}
diff --git a/drivers/firmware/socfpga-fcs.c b/drivers/firmware/socfpga-fcs.c
new file mode 100644
index 000000000000..cfe9b25faf30
--- /dev/null
+++ b/drivers/firmware/socfpga-fcs.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026, Altera Corporation
+ */
+
+#include <linux/firmware/intel/socfpga-fcs.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+
+/**
+ * open_session_store() - open an FCS crypto service session
+ * @dev: pointer to fcs device
+ * @attr: device attribute
+ * @buf: pointer to character buffer carrying the user command context
+ * @buf_size: size of the buffer
+ *
+ * Return: @buf_size on success, negative errno on failure.
+ */
+static ssize_t open_session_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t buf_size)
+{
+ struct fcs_cmd_context *const u_ctx = *(struct fcs_cmd_context **)buf;
+ struct fcs_cmd_context *k_ctx;
+ int ret;
+
+ k_ctx = fcs_acquire_cmd_ctx();
+ if (!k_ctx) {
+ dev_err(dev, "Failed get context. Context is in use\n");
+ return -EBUSY;
+ }
+
+ if (copy_from_user(k_ctx, u_ctx, sizeof(*k_ctx))) {
+ dev_err(dev, "Failed to copy context from user space\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ ret = fcs_session_open(k_ctx);
+ if (ret) {
+ dev_err(dev, "Failed to open session\n");
+ goto out;
+ }
+
+ ret = buf_size;
+out:
+ fcs_release_cmd_ctx(k_ctx);
+
+ return ret;
+}
+
+/**
+ * close_session_store() - close an FCS crypto service session
+ * @dev: pointer to fcs device
+ * @attr: device attribute
+ * @buf: pointer to character buffer carrying the user command context
+ * @buf_size: size of the buffer
+ *
+ * Return: @buf_size on success, negative errno on failure.
+ */
+static ssize_t close_session_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t buf_size)
+{
+ struct fcs_cmd_context *const u_ctx = *(struct fcs_cmd_context **)buf;
+ struct fcs_cmd_context *k_ctx;
+ int ret;
+
+ k_ctx = fcs_acquire_cmd_ctx();
+ if (!k_ctx) {
+ dev_err(dev, "Failed get context. Context is in use\n");
+ return -EBUSY;
+ }
+
+ if (copy_from_user(k_ctx, u_ctx, sizeof(*k_ctx))) {
+ dev_err(dev, "Failed to copy context from user space\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ ret = fcs_session_close(k_ctx);
+ if (ret) {
+ dev_err(dev, "Failed to close session\n");
+ goto out;
+ }
+
+ ret = buf_size;
+out:
+ fcs_release_cmd_ctx(k_ctx);
+
+ return ret;
+}
+
+/**
+ * atf_version_show() - report the Arm Trusted Firmware build version
+ * @dev: pointer to fcs device
+ * @attr: device attribute
+ * @buf: pointer to character buffer to receive the version string
+ *
+ * Return: number of bytes written to @buf.
+ */
+static ssize_t atf_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int version[3];
+
+ fcs_get_atf_version(version);
+ return sysfs_emit(buf, "%u.%u.%u\n", version[0], version[1], version[2]);
+}
+
+/**
+ * platform_show() - report the detected FCS platform identifier
+ * @dev: pointer to fcs device
+ * @attr: device attribute
+ * @buf: pointer to character buffer to receive the platform identifier
+ *
+ * Return: number of bytes written to @buf.
+ */
+static ssize_t platform_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%d\n", fcs_get_platform());
+}
+
+/**
+ * sdos_store() - perform an SDOS encrypt/decrypt operation
+ * @dev: pointer to fcs device
+ * @attr: device attribute
+ * @buf: pointer to character buffer carrying the user command context
+ * @buf_size: size of the buffer
+ *
+ * Return: @buf_size on success, negative errno on failure.
+ */
+static ssize_t sdos_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t buf_size)
+{
+ struct fcs_cmd_context *const u_ctx = *(struct fcs_cmd_context **)buf;
+ struct fcs_cmd_context *k_ctx;
+ int ret;
+
+ k_ctx = fcs_acquire_cmd_ctx();
+ if (!k_ctx) {
+ dev_err(dev, "Failed get context. Context is in use\n");
+ return -EBUSY;
+ }
+
+ if (copy_from_user(k_ctx, u_ctx, sizeof(*k_ctx))) {
+ dev_err(dev, "Failed to copy context from user space\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ ret = fcs_sdos_crypt(k_ctx);
+ if (ret) {
+ dev_err(dev, "Failed to perform SDOS operation\n");
+ goto out;
+ }
+
+ ret = buf_size;
+out:
+ fcs_release_cmd_ctx(k_ctx);
+
+ return ret;
+}
+
+static DEVICE_ATTR_WO(open_session);
+static DEVICE_ATTR_WO(close_session);
+static DEVICE_ATTR_RO(atf_version);
+static DEVICE_ATTR_WO(sdos);
+static DEVICE_ATTR_RO(platform);
+
+static struct attribute *fcs_attrs[] = {
+ &dev_attr_open_session.attr,
+ &dev_attr_close_session.attr,
+ &dev_attr_atf_version.attr,
+ &dev_attr_sdos.attr,
+ &dev_attr_platform.attr,
+ NULL
+};
+
+static struct attribute_group fcs_group = {
+ .attrs = fcs_attrs,
+};
+
+static const struct attribute_group *fcs_groups[] = {
+ &fcs_group,
+ NULL,
+};
+
+static struct kobject *sysfs_kobj;
+
+/**
+ * fcs_driver_probe() - probe the FCS platform device
+ * @pdev: pointer to the FCS platform device
+ *
+ * Initialise the FCS state and publish the sysfs attribute groups.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+static int fcs_driver_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ /*
+ * Initialise the private state before publishing any sysfs
+ * attribute, otherwise a concurrent access could dereference a
+ * not-yet-allocated priv.
+ */
+ ret = fcs_init(dev);
+ if (ret) {
+ dev_err(dev, "Failed to initialize FCS\n");
+ return ret;
+ }
+
+ sysfs_kobj = kobject_create_and_add("fcs_sysfs", kernel_kobj);
+ if (!sysfs_kobj) {
+ dev_err(dev, "Failed to create and add kobject\n");
+ ret = -ENOMEM;
+ goto err_deinit;
+ }
+
+ ret = sysfs_create_groups(sysfs_kobj, fcs_groups);
+ if (ret) {
+ dev_err(dev, "Failed to create sysfs groups\n");
+ kobject_put(sysfs_kobj);
+ sysfs_kobj = NULL;
+ goto err_deinit;
+ }
+
+ return 0;
+
+err_deinit:
+ fcs_deinit();
+
+ return ret;
+}
+
+/**
+ * fcs_driver_remove() - remove the FCS platform device
+ * @pdev: pointer to the FCS platform device
+ *
+ * Remove the sysfs attribute groups and tear down the FCS state.
+ */
+static void fcs_driver_remove(struct platform_device *pdev)
+{
+ sysfs_remove_groups(sysfs_kobj, fcs_groups);
+ kobject_put(sysfs_kobj);
+ sysfs_kobj = NULL;
+
+ fcs_deinit();
+}
+
+static struct platform_driver fcs_driver = {
+ .probe = fcs_driver_probe,
+ .remove = fcs_driver_remove,
+ .driver = {
+ .name = "stratix10-fcs",
+ },
+};
+
+/**
+ * socfpga_fcs_init() - register the FCS platform driver
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+static int __init socfpga_fcs_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&fcs_driver);
+ if (ret)
+ pr_err("Failed to register platform driver: %d\n", ret);
+
+ return ret;
+}
+
+/**
+ * socfpga_fcs_exit() - unregister the FCS platform driver
+ */
+static void __exit socfpga_fcs_exit(void)
+{
+ platform_driver_unregister(&fcs_driver);
+}
+
+module_init(socfpga_fcs_init);
+module_exit(socfpga_fcs_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Altera SoCFPGA FCS SDOS encrypt/decrypt driver");
+MODULE_AUTHOR("Altera Corporation");
+MODULE_ALIAS("platform:stratix10-fcs");
diff --git a/include/linux/firmware/intel/socfpga-fcs.h b/include/linux/firmware/intel/socfpga-fcs.h
new file mode 100644
index 000000000000..e263d6ee8cc7
--- /dev/null
+++ b/include/linux/firmware/intel/socfpga-fcs.h
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 Altera Corporation
+ *
+ * SDOS-only subset of the SoCFPGA FCS (FPGA Crypto Service) interface,
+ * shared between the driver front-end (socfpga-fcs.c) and the command
+ * engine (socfpga-fcs-core.c).
+ */
+#ifndef SOCFPGA_FCS_H
+#define SOCFPGA_FCS_H
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/uuid.h>
+#include <linux/firmware/intel/stratix10-svc-client.h>
+
+#define SDOS_HEADER_SZ 40
+#define SDOS_HMAC_SZ 48
+#define SDOS_PLAINDATA_MIN_SZ 32
+#define SDOS_PLAINDATA_MAX_SZ 32672
+#define SDOS_DECRYPTED_MIN_SZ (SDOS_PLAINDATA_MIN_SZ + SDOS_HEADER_SZ)
+#define SDOS_DECRYPTED_MAX_SZ (SDOS_PLAINDATA_MAX_SZ + SDOS_HEADER_SZ)
+#define SDOS_ENCRYPTED_MIN_SZ (SDOS_PLAINDATA_MIN_SZ + SDOS_HEADER_SZ + SDOS_HMAC_SZ)
+#define SDOS_ENCRYPTED_MAX_SZ (SDOS_PLAINDATA_MAX_SZ + SDOS_HEADER_SZ + SDOS_HMAC_SZ)
+
+/* Platform definitions (also exposed via the "platform" sysfs attribute) */
+#define AGILEX5_PLAT 2
+
+#pragma pack(push, 1)
+struct fcs_cmd_context {
+ /* Error status variable address */
+ int *error_code_addr;
+ union {
+ struct {
+ uuid_t *suuid;
+ unsigned int *suuid_len;
+ } open_session;
+
+ struct {
+ uuid_t suuid;
+ } close_session;
+
+ struct {
+ uuid_t suuid;
+ u32 context_id;
+ char *rng;
+ u32 rng_len;
+ } rng;
+
+ struct {
+ uuid_t suuid;
+ u32 context_id;
+ u32 op_mode;
+ char *src;
+ u32 src_size;
+ char *dst;
+ u32 *dst_size;
+ u16 id;
+ u64 own;
+ int pad;
+ } sdos;
+ };
+};
+
+#pragma pack(pop)
+
+/**
+ * Private driver state for the SoCFPGA FCS that holds the SDM/ATF service
+ * channel, the shared command context and the lock that guards it, and the
+ * latest mailbox status/response.
+ */
+struct socfpga_fcs_priv {
+ /* Communication channel */
+ struct stratix10_svc_chan *chan;
+ u32 platform;
+ struct fcs_cmd_context k_ctx;
+ struct stratix10_svc_client client;
+ struct completion completion;
+ /*
+ * Serializes FCS command submission: guards the shared k_ctx and the
+ * single in-flight mailbox transaction (completion/status/resp) so only
+ * one SDM request is outstanding at a time. This is the lock taken by
+ * fcs_acquire_cmd_ctx() and dropped by fcs_release_cmd_ctx().
+ */
+ struct mutex lock;
+ int status;
+ u32 resp;
+ u32 session_id;
+ uuid_t uuid_id;
+ struct device *dev;
+ u32 atf_version[3];
+};
+
+enum fcs_command_code {
+ FCS_DEV_COMMAND_NONE = 0,
+ FCS_DEV_CRYPTO_OPEN_SESSION,
+ FCS_DEV_CRYPTO_CLOSE_SESSION,
+ FCS_DEV_SDOS_DATA_EXT,
+ FCS_DEV_ATF_VERSION,
+};
+
+/* Take the FCS lock and return the shared command context. */
+struct fcs_cmd_context *fcs_acquire_cmd_ctx(void);
+
+/* Release the FCS lock previously taken by fcs_acquire_cmd_ctx(). */
+void fcs_release_cmd_ctx(struct fcs_cmd_context *const k_ctx);
+
+/* Return the detected FCS platform identifier. */
+int fcs_get_platform(void);
+
+/* Allocate the FCS state and set up the service channel; read ATF version. */
+int fcs_init(struct device *dev);
+
+/* Close any open session and release the service channel. */
+void fcs_deinit(void);
+
+/* Release the service channel and clear the FCS state. */
+void fcs_cleanup(void);
+
+/* Request the SDM to open a crypto service session. */
+int fcs_session_open(struct fcs_cmd_context *const k_ctx);
+
+/* Request the SDM to close a previously opened session. */
+int fcs_session_close(struct fcs_cmd_context *const k_ctx);
+
+/* Return the cached Arm Trusted Firmware build version. */
+void fcs_get_atf_version(u32 *version);
+
+/* Perform an SDOS (Secure Data Object Service) encrypt/decrypt operation. */
+int fcs_sdos_crypt(struct fcs_cmd_context *const k_ctx);
+
+#endif /* SOCFPGA_FCS_H */
--
2.43.7
^ permalink raw reply related [flat|nested] 3+ messages in thread