diff options
Diffstat (limited to 'drivers/firmware/imx/se_ctrl.h')
| -rw-r--r-- | drivers/firmware/imx/se_ctrl.h | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/drivers/firmware/imx/se_ctrl.h b/drivers/firmware/imx/se_ctrl.h new file mode 100644 index 000000000000..c05328f3b408 --- /dev/null +++ b/drivers/firmware/imx/se_ctrl.h @@ -0,0 +1,309 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2026 NXP + */ + +#ifndef SE_CTRL_H +#define SE_CTRL_H + +#include <linux/bitfield.h> +#include <linux/miscdevice.h> +#include <linux/mailbox_client.h> +#include <linux/semaphore.h> +#include <linux/workqueue.h> + +#define MAX_DEVNAME_SZ 64 +#define MAX_FW_LOAD_RETRIES 50 +#define SE_MSG_WORD_SZ 0x4 + +#define RES_STATUS(x) FIELD_GET(0x000000ff, x) +#define MAX_DATA_SIZE_PER_USER (128 * 1024) +#define MAX_NVM_MSG_LEN (256) +/* Largest firmware response buffer size in bytes; equals ELE_DEBUG_DUMP_RSP_SZ (0x5c). */ +#define MAX_ALLOWED_RX_MSG_SZ 0x5c +#define MESSAGING_VERSION_6 0x6 +#define MESSAGING_VERSION_7 0x7 + +struct se_if_open_gate { + struct miscdevice miscdev; + struct se_if_priv *priv; + /* to lock to update the structure */ + struct mutex lock; + struct kref refcount; + bool dying; + /* set once misc_register() has succeeded (deferred to probe end) */ + bool registered; +}; + +struct se_clbk_handle { + struct se_if_device_ctx *dev_ctx; + struct completion done; + bool signal_rcvd; + /* + * Set under clbk_rx_lock once a real response is copied into rx_msg, + * cleared when a new transaction is armed. Lets ele_msg_rcv() tell a + * genuine response from a teardown-forced complete_all() with no data. + */ + bool rx_delivered; + u32 rx_msg_sz; + + /* + * Assignment of the rx_msg buffer to held till the + * received content as part callback function, is copied. + */ + struct se_api_msg *rx_msg; + /* + * Serialise the timeout path in ele_msg_rcv() against + * se_if_rx_callback() so that the callback can never + * memcpy into a buffer that the timeout path has already + * freed. + */ + spinlock_t clbk_rx_lock; +}; + +struct se_imem_buf { + u8 *buf; + dma_addr_t daddr; + u32 size; + u32 state; +}; + +struct se_buf_desc { + u8 *shared_buf_ptr; + void __user *usr_buf_ptr; + u32 size; + struct list_head link; +}; + +struct se_shared_mem { + dma_addr_t dma_addr; + u32 size; + u32 pos; + u8 *ptr; +}; + +struct se_shared_mem_mgmt_info { + struct list_head mem_pool_buf_list; + struct list_head pending_in; + struct list_head pending_out; + + struct se_shared_mem non_secure_mem; +}; + +/* Private struct for each char device instance. */ +struct se_if_device_ctx { + struct se_if_priv *priv; + struct miscdevice *miscdev; + const char *devname; + u32 sess_hdl; + u32 strg_hdl; + bool cleanup_done; + unsigned long rcv_msg_timeout_jiffies; + + /* process one file operation at a time. */ + struct mutex fops_lock; + + struct se_shared_mem_mgmt_info se_shared_mem_mgmt; + struct list_head link; + + /* Add reference counting */ + struct kref refcount; +}; + +/* Header of the messages exchange with the EdgeLock Enclave */ +struct se_msg_hdr { + u8 ver; + u8 size; + u8 command; + u8 tag; +} __packed; + +#define SE_MU_HDR_SZ 4 +#define SE_MU_HDR_WORD_SZ 1 + +struct se_api_msg { + struct se_msg_hdr header; + u32 data[]; +}; + +struct se_if_defines { + const u8 se_if_type; + u8 cmd_tag; + u8 rsp_tag; + u8 success_tag; + u8 base_api_ver; + u8 fw_api_ver; +}; + +struct se_fw_img_name { + const char *prim_fw_nm_in_rfs; + const char *seco_fw_nm_in_rfs; +}; + +struct se_fw_load_info { + const struct se_fw_img_name *se_fw_img_nm; + bool is_fw_tobe_loaded; + bool imem_mgmt; + struct se_imem_buf imem; + /* to serialize the fw load state */ + struct mutex load_fw_lock; +}; + +struct cmd_rcvr_data_info { + /* + * Tracks the last FW export command received by the command receiver + * (ELE_STORAGE_MASTER_EXPORT_REQ or ELE_STORAGE_CHUNK_EXPORT_REQ). + * Set by cmd_receiver_specific_ops() when the FW command arrives via + * read(), cleared at entry. se_cmd_receiver_allowed_rsp() checks it to + * ensure write() can only follow a matching read() for export responses. + * Stored per SE interface (not file-scope static) to prevent a race when + * multiple SE interfaces (e.g. ELE and V2X) run concurrent export flows. + */ + u8 cmd_rcvr_last_rcvd_cmd_id; + + /* + * Export buffer size communicated by the FW in the preceding + * ELE_STORAGE_MASTER_EXPORT_REQ or ELE_STORAGE_CHUNK_EXPORT_REQ + * command. cmd_receiver_specific_ops() stores it here; se_val_cmd_addrs() + * reads it when size_idx == SE_CMD_RCVR_ADDR_VAR_SIZE to range-check + * the response buffer. Stored per SE interface so concurrent ELE and V2X + * export flows cannot corrupt each other's size. + */ + u32 cmd_rcvr_var_size; +}; + +struct fw_busy_info { + /* + * fw_busy acts as a circuit breaker: set when a synchronous + * transaction times out, cleared when the late FW response + * arrives and se_clear_fw_busy() has reclaimed the parked + * dev_ctx. + */ + atomic_t fw_busy; + + /* + * Serialise fw_busy, fw_busy_dev_ctx state updates between the + * timeout path, late-response callback/work, and teardown. + */ + spinlock_t fw_busy_lock; + + /* + * dev_ctx whose synchronous transaction timed out; parked here + * so a late FW response can still be routed to it by + * se_clear_fw_busy(). + */ + struct se_if_device_ctx *fw_busy_dev_ctx; + + /* + * Snapshot of fw_busy_dev_ctx->devname, captured under + * fw_busy_lock at arm time in se_mark_fw_busy(). cleanup_dev_ctx() + * frees dev_ctx->devname (and sets it to NULL) when a userspace fd + * is closed while the breaker is armed, so the parked dev_ctx may + * carry a NULL devname by the time se_clear_fw_busy() runs on a late + * FW response. Use this stable copy for diagnostics instead. Sized to + * match the "%s0_ch%d" devname and the char devname_snap[32] buffers + * used elsewhere in this driver. + */ + char devname[MAX_DEVNAME_SZ]; + + /* work item scheduled by se_if_rx_callback() on late response. */ + struct work_struct fw_busy_work; + + /* + * Snapshot of the orphaned firmware response that triggered + * fw_busy_work. Written once (under clbk_rx_lock, before + * schedule_work()) and read once (in se_clear_fw_busy(), under + * clbk_rx_lock). Sized to MAX_ALLOWED_RX_MSG_SZ (= ELE_DEBUG_DUMP_RSP_SZ, + * the largest firmware response) to accommodate any FW reply. + */ + u8 orphan_fw_rx_msg[MAX_ALLOWED_RX_MSG_SZ]; +}; + +struct msg_excl_flow_info { + /* + * Optional exclusive reservation of the SE messaging interface for a + * single flow. A flow that needs ele_msg_send_rcv() reserved to itself + * (e.g. the fw_busy recovery in se_clear_fw_busy()) publishes its own + * task here via se_reserve_msg_if() from OUTSIDE ele_msg_send_rcv(), and + * releases it with se_release_msg_if() when done. While non-NULL, + * ele_msg_send_rcv() lets only this owning task through and rejects every + * other caller with -EBUSY; while NULL the interface is open to general + * se_if_cmd_lock-based message exchange. Written under msg_excl_lock, + * read locklessly in ele_msg_send_rcv() via READ_ONCE and only ever + * compared against current, so a stale read is harmless (a non-owner can + * never match). + */ + struct task_struct *msg_excl_owner; + /* Serialise updates to msg_excl_owner. */ + spinlock_t msg_excl_lock; + /* + * Serialises competing reservation flows. se_reserve_msg_if() holds + * this mutex for the whole duration of a reservation, so a second flow + * that wants exclusive ownership of the messaging interface sleeps here + * until the current owner calls se_release_msg_if(). Held only from + * process/workqueue context. + */ + struct mutex msg_excl_flow_lock; +}; + +struct se_if_priv { + struct device *dev; + + struct se_clbk_handle cmd_receiver_clbk_hdl; + /* + * Update to the waiting_rsp_dev, to be protected + * under se_if_cmd_lock. + */ + struct se_clbk_handle waiting_rsp_clbk_hdl; + + /* + * prevent new command to be sent on the se interface while previous + * command is still processing. (response is awaited) + */ + struct mutex se_if_cmd_lock; + + struct msg_excl_flow_info msg_excl_flow; + struct mbox_client se_mb_cl; + struct mbox_chan *tx_chan, *rx_chan; + + struct gen_pool *mem_pool; + const struct se_if_defines *if_defs; + struct se_fw_load_info load_fw; + + /* + * Set once teardown begins. New synchronous transactions are rejected + * and a teardown-forced completion is not mistaken for a real firmware + * response. + */ + atomic_t going_away; + struct fw_busy_info fw_busy_info; + + struct se_if_device_ctx *priv_dev_ctx; + struct list_head dev_ctx_list; + + /* prevent modifying priv member variable in parallel. */ + struct mutex modify_lock; + u32 active_devctx_count; + u32 dev_ctx_mono_count; + + /* Add reference counting */ + struct kref refcount; + + /* stable gate used by .open() */ + struct se_if_open_gate *open_gate; + struct cmd_rcvr_data_info crcvr_info; +}; + +char *get_se_if_name(u8 se_if_id); +void unset_dev_ctx_as_command_receiver(struct se_if_device_ctx *dev_ctx); +int set_dev_ctx_as_command_receiver(struct se_if_device_ctx *dev_ctx); +bool se_is_fw_busy_ctx(struct se_if_device_ctx *dev_ctx); +void se_dev_ctx_shared_mem_cleanup(struct se_if_device_ctx *dev_ctx); +int get_shared_mem_slot(struct se_if_device_ctx *dev_ctx, + u32 *length, dma_addr_t *ele_dma_addr, void **ptr); +int se_get_mem_pool_buf(struct se_if_device_ctx *dev_ctx, void **buf, + dma_addr_t *daddr, u32 len); +void se_cleanup_mem_pool_buf(struct se_if_device_ctx *dev_ctx, bool reclaim); +int se_reserve_msg_if(struct se_if_priv *priv); +void se_release_msg_if(struct se_if_priv *priv); +#endif |
