From f1044e3793aee7782e8fe8e13da8d3c4d07a1ede Mon Sep 17 00:00:00 2001 From: Ajay Neeli Date: Mon, 17 Mar 2025 12:11:29 +0530 Subject: [PATCH 01/10] scsi: Add Initial Support for SCSI Subsytem Introduce core components of the SCSI subsystem, including support for device initialization, command dispatching, and basic I/O operations. Provide foundation for future integration with UFS and other SCSI-based devices. Signed-off-by: Ajay Neeli --- include/zephyr/scsi/scsi.h | 209 ++++++++++++++++ subsys/CMakeLists.txt | 1 + subsys/Kconfig | 1 + subsys/scsi/CMakeLists.txt | 10 + subsys/scsi/Kconfig | 27 ++ subsys/scsi/scsi.c | 488 +++++++++++++++++++++++++++++++++++++ 6 files changed, 736 insertions(+) create mode 100644 include/zephyr/scsi/scsi.h create mode 100644 subsys/scsi/CMakeLists.txt create mode 100644 subsys/scsi/Kconfig create mode 100644 subsys/scsi/scsi.c diff --git a/include/zephyr/scsi/scsi.h b/include/zephyr/scsi/scsi.h new file mode 100644 index 000000000000..6bec430aba06 --- /dev/null +++ b/include/zephyr/scsi/scsi.h @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_SCSI_H_ +#define ZEPHYR_INCLUDE_SCSI_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* MACROS */ + +/* SCSI Command Operation Codes */ +#define SCSI_TST_U_RDY 0x00 /* Test Unit Ready */ +#define SCSI_READ10 0x28 /* Read 10-byte */ +#define SCSI_READ16 0x88 /* Read 16-byte */ +#define SCSI_WRITE10 0x2A /* Write 10-byte */ +#define SCSI_WRITE16 0x8A /* Write 16-byte */ + +/* Maximum Number of Retries for SCSI Cmds */ +#define SCSI_MAX_RETRIES (3) + +/* Maximum size of the Command Descriptor Block (CDB) */ +#define MAX_CDB_SIZE (16U) + +/* SCSI IOCTL Command Codes */ +#define SCSI_IOCTL_TEST_UNIT_READY (0x02) /* checks if the device is ready */ +#define SG_IO (0x85) /* issue SCSI commands to the device */ + +/* Block Storage Generic (BSG) Protocol Constants */ +#define BSG_PROTOCOL_SCSI (0U) +#define BSG_SUB_PROTOCOL_SCSI_CMD (0U) +#define BSG_SUB_PROTOCOL_SCSI_TRANSPORT (1U) + +/* Data Transfer Directions for SCSI Generic (SG) Commands */ +#define SG_DXFER_NONE (1) +#define SG_DXFER_TO_DEV (2) +#define SG_DXFER_FROM_DEV (3) + +/* STRUCTURES */ + +/** + * @brief Structure to hold information about a SCSI command to be processed + */ +struct scsi_cmd { + uint8_t cmd[MAX_CDB_SIZE]; /**< Command descriptor block (CDB) */ + uint8_t lun; /**< Target LUN (Logical Unit Number) */ + uint8_t cmdlen; /**< Length of the command */ + uint64_t datalen; /**< Total length of data to be transferred */ + uint8_t *pdata; /**< Pointer to data to be transferred */ + uint8_t dma_dir; /**< Direction of data transfer (e.g., read/write) */ +} __packed; + +/** + * @brief Structure representing LUN (Logical Unit Number) information. + */ +struct lun_info { + uint32_t lun_id; /**< ID of the LUN */ + bool lun_enabled; /**< Indicates if the LUN is enabled */ + uint32_t block_size; /**< Size of each block in bytes */ + uint64_t block_count; /**< Total number of blocks in the LUN */ +} __packed; + +/** + * @brief Information about a SCSI host controller. + */ +struct scsi_host_info { + sys_slist_t sdevices; /**< List of connected SCSI devices */ + const struct scsi_ops *ops; /**< Pointer to the SCSI operations structure */ + void *hostdata; /**< Pointer to host-specific data */ + struct device *parent; /**< Parent device associated with the host */ +}; + +/** + * @brief Struct representing a SCSI device + */ +struct scsi_device { + sys_snode_t node; /**< Node in the list of SCSI devices */ + struct scsi_host_info *host; /**< Pointer to the associated SCSI host controller */ + uint8_t lun; /**< LUN ID for the device */ + uint32_t sector_size; /**< Size of sectors in bytes */ + uint32_t capacity; /**< Total capacity of the device in blocks */ +}; + +/** + * @brief Structure to represent a SCSI Generic (SG) I/O request + */ +struct sg_io_req { + uint32_t protocol; /**< SCSI protocol type */ + uint32_t subprotocol; /**< Sub-protocol type (0 - SCSI command, 1 - SCSI transport) */ + void *request; /**< Pointer to the SCSI CDB or transport layer request */ + uint32_t request_len; /**< Length of the request in bytes */ + void *response; /**< Pointer to the response buffer */ + uint32_t max_response_len; /**< Maximum length of the response buffer */ + int32_t dxfer_dir; /**< Data transfer direction */ + uint32_t dxfer_len; /**< Length of the data to transfer */ + void *dxferp; /**< Pointer to the data to be transferred */ +}; + +/** + * @brief Operations for managing SCSI commands + */ +struct scsi_ops { + /** + * @brief Execute a SCSI command on a given SCSI device. + * + * @param sdev Pointer to the SCSI device handle + * @param cmd Pointer to the SCSI command to execute + * + * @return 0 on success, negative value on error + */ + int (*exec)(struct scsi_device *sdev, struct scsi_cmd *cmd); +}; + +/* FUNCTION PROTOTYPES */ + +/** + * @brief Perform a write operation on the SCSI device. + * + * This function sends a write command to the SCSI device. + * + * @param scsi_dev Pointer to the scsi_device structure. + * @param sector Starting sector number. + * @param count Number of sectors to write. + * @param buf Pointer to the data buffer to write. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_write(struct scsi_device *scsi_dev, uint64_t sector, uint32_t count, const uint8_t *buf); + +/** + * @brief Perform a read operation on the SCSI device. + * + * This function sends a read command to the SCSI device. + * + * @param scsi_dev Pointer to the scsi_device structure. + * @param sector Starting sector number. + * @param count Number of sectors to read. + * @param buf Pointer to the buffer to store the read data. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_read(struct scsi_device *scsi_dev, uint64_t sector, uint32_t count, uint8_t *buf); + +/** + * @brief Allocate a new scsi_host_info. + * + * This function allocates and initializes a new scsi_host_info structure, + * setting the operations field to the given scsi operations. + * + * @param sops Pointer to the scsi operations structure. + * + * @return Pointer to the newly allocated scsi_host_info, or NULL on failure. + */ +struct scsi_host_info *scsi_host_alloc(const struct scsi_ops *sops); + +/** + * @brief Add a new LUN to the SCSI host. + * + * This function adds a new LUN to the host, initializing the associated + * scsi_device if necessary. It also stores the sector size and capacity + * for the device. + * + * @param shost Pointer to the SCSI host. + * @param lun Pointer to the lun_info structure containing LUN information. + * + * @return 0 on success, or a negative error code on failure. + */ +int scsi_add_lun_host(struct scsi_host_info *shost, struct lun_info *lun); + +/** + * @brief Find a SCSI device by host and LUN. + * + * This function searches the list of devices attached to the given host + * and returns the device with the specified LUN. + * + * @param shost Pointer to the SCSI host. + * @param lun Logical Unit Number (LUN) of the SCSI device. + * + * @return Pointer to the scsi_device structure, or NULL if no matching + * device is found. + */ +struct scsi_device *scsi_device_lookup_by_host(struct scsi_host_info *shost, uint32_t lun); + +/** + * @brief Dispatch an ioctl to a SCSI device. + * + * This function dispatches the appropriate ioctl to the SCSI device based on + * the provided command. + * + * @param sdev Pointer to the scsi_device structure. + * @param cmd The ioctl command. + * @param arg Data associated with the ioctl. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_ioctl(struct scsi_device *sdev, int32_t cmd, void *arg); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_SCSI_H_ */ diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index 61f7f3cf940d..3ccb53753340 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -51,6 +51,7 @@ add_subdirectory_ifdef(CONFIG_MODEM_MODULES modem) add_subdirectory_ifdef(CONFIG_NETWORKING net) add_subdirectory_ifdef(CONFIG_PROFILING profiling) add_subdirectory_ifdef(CONFIG_RETENTION retention) +add_subdirectory_ifdef(CONFIG_SCSI scsi) add_subdirectory_ifdef(CONFIG_SECURE_STORAGE secure_storage) add_subdirectory_ifdef(CONFIG_SENSING sensing) add_subdirectory_ifdef(CONFIG_SETTINGS settings) diff --git a/subsys/Kconfig b/subsys/Kconfig index 93880693f51b..dcf8580d2f74 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -38,6 +38,7 @@ source "subsys/profiling/Kconfig" source "subsys/random/Kconfig" source "subsys/retention/Kconfig" source "subsys/rtio/Kconfig" +source "subsys/scsi/Kconfig" source "subsys/sd/Kconfig" source "subsys/secure_storage/Kconfig" source "subsys/sensing/Kconfig" diff --git a/subsys/scsi/CMakeLists.txt b/subsys/scsi/CMakeLists.txt new file mode 100644 index 000000000000..537cb0550f54 --- /dev/null +++ b/subsys/scsi/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +if (CONFIG_SCSI) + zephyr_interface_library_named(SCSI) + + zephyr_library() + zephyr_library_sources(scsi.c) + +endif() diff --git a/subsys/scsi/Kconfig b/subsys/scsi/Kconfig new file mode 100644 index 000000000000..260a107a568e --- /dev/null +++ b/subsys/scsi/Kconfig @@ -0,0 +1,27 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +# SCSI Subsystem Configuration +# This option enables support for SCSI controllers, which are commonly +# used in storage devices such as hard drives, optical drives, and more. +# Enabling this option will allow the system to use SCSI-based storage +# protocols and commands. + +menuconfig SCSI + bool "Support SCSI controllers" + default n + help + Enable this option if you want to support SCSI (Small Computer + System Interface) controllers on your platform. This will allow + the system to communicate with SCSI devices and manage storage. + +if SCSI + +# Define the SCSI subsystem as a module +module = SCSI +module-str = scsi + +# Include additional logging configuration for SCSI subsystem +source "subsys/logging/Kconfig.template.log_config" + +endif # SCSI diff --git a/subsys/scsi/scsi.c b/subsys/scsi/scsi.c new file mode 100644 index 000000000000..a5db10d25bb5 --- /dev/null +++ b/subsys/scsi/scsi.c @@ -0,0 +1,488 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +LOG_MODULE_REGISTER(scsi, CONFIG_SCSI_LOG_LEVEL); + +/* + * This file implements SCSI subsystem support to + * interface with Storage Host Controllers like UFS + * + * supports SCSI cmds + * - TEST_UNIT_READY + * - READ + * - WRITE + */ + +/** + * @brief Dispatch a command to the low-level driver. + * + * This function calls the low-level driver to execute the specified SCSI + * command. The execution is done via the appropriate function in the + * host's operations (sdev->host->ops->exec). + * + * @param sdev Pointer to the scsi_device structure. + * @param cmd Pointer to the scsi_cmd structure to be dispatched. + * + * @return 0 on success, or a negative error code on failure. + */ +static int scsi_exec(struct scsi_device *sdev, struct scsi_cmd *cmd) +{ + const struct scsi_ops *ops = (const struct scsi_ops *)sdev->host->ops; + + if (ops->exec == NULL) { + return -EINVAL; + } + + /* Execute the command if supported */ + return ops->exec(sdev, cmd); +} + +/** + * @brief Allocate a new scsi_host_info. + * + * This function allocates and initializes a new scsi_host_info structure, + * setting the operations field to the given scsi operations. + * + * @param sops Pointer to the scsi operations structure. + * + * @return Pointer to the newly allocated scsi_host_info, or NULL on failure. + */ +struct scsi_host_info *scsi_host_alloc(const struct scsi_ops *sops) +{ + struct scsi_host_info *shost = NULL; + + /* Allocate memory for the SCSI host structure */ + shost = (struct scsi_host_info *)k_malloc(sizeof(struct scsi_host_info)); + if (shost == NULL) { + return NULL; + } + + /* Initialize the list of devices and set host operations */ + (void)memset(shost, 0, sizeof(struct scsi_host_info)); + sys_slist_init(&shost->sdevices); + shost->ops = sops; + + return shost; +} + +/** + * @brief Find a SCSI device by host and LUN. + * + * This function searches the list of devices attached to the given host + * and returns the device with the specified LUN. + * + * @param shost Pointer to the SCSI host. + * @param lun Logical Unit Number (LUN) of the SCSI device. + * + * @return Pointer to the scsi_device structure, or NULL if no matching + * device is found. + */ +struct scsi_device *scsi_device_lookup_by_host(struct scsi_host_info *shost, + uint32_t lun) +{ + struct scsi_device *sdev = NULL, *itr_sdev; + sys_snode_t *itr_node; + + /* Iterate through the list of devices attached to the host */ + SYS_SLIST_FOR_EACH_NODE(&shost->sdevices, itr_node) { + itr_sdev = (struct scsi_device *)CONTAINER_OF(itr_node, + struct scsi_device, + node); + + /* Check if the device matches the given LUN. */ + if ((itr_sdev->lun) == lun) { + sdev = itr_sdev; + break; + } + } + + return sdev; +} + +/** + * @brief Allocate and initialize a scsi_device. + * + * This function allocates and initializes a new scsi_device structure + * for the given host and LUN, and appends it to the host's device list. + * + * @param shost Pointer to the SCSI host. + * @param lun Logical Unit Number (LUN) for the new SCSI device. + * + * @return Pointer to the newly allocated scsi_device, or NULL on failure. + */ +static struct scsi_device *scsi_alloc_sdev(struct scsi_host_info *shost, + uint32_t lun) +{ + struct scsi_device *sdev; + + /* Allocate memory for the new device structure */ + sdev = (struct scsi_device *)k_malloc(sizeof(struct scsi_device)); + if (sdev == NULL) { + return NULL; + } + + /* Associate the device with the host and set its LUN */ + (void)memset(sdev, 0, sizeof(struct scsi_device)); + sdev->host = shost; + sdev->lun = (uint8_t)lun; + + /* Append the new device to the host's device list */ + sys_slist_append(&shost->sdevices, &sdev->node); + + return sdev; +} + +/** + * @brief Add a new LUN to the SCSI host. + * + * This function adds a new LUN to the host, initializing the associated + * scsi_device if necessary. It also stores the sector size and capacity + * for the device. + * + * @param shost Pointer to the SCSI host. + * @param luninfo Pointer to the lun_info structure containing LUN information. + * + * @return 0 on success, or a negative error code on failure. + */ +int scsi_add_lun_host(struct scsi_host_info *shost, struct lun_info *lun) +{ + struct scsi_device *sdev; + int res = 0; + + /* Check if the LUN information is valid and enabled */ + if ((lun == NULL) || (lun->lun_enabled == false)) { + res = -EINVAL; + goto out; + } + + /* Try to find the device associated with the given LUN */ + sdev = scsi_device_lookup_by_host(shost, lun->lun_id); + if (sdev == NULL) { + /* If no device found, allocate a new one for this LUN */ + sdev = scsi_alloc_sdev(shost, lun->lun_id); + } + + if (sdev == NULL) { + res = -ENOMEM; + goto out; + } + + /* Save sector size and capacity based on the LUN info */ + sdev->sector_size = lun->block_size; + sdev->capacity = (uint32_t)lun->block_count; + +out: + return res; +} + +/** + * @brief Frame the "Test Unit Ready" SCSI command. + * + * This function populates the command block for a "Test Unit Ready" + * command (SCSI TST_U_RDY). + * + * @param pccb: Pointer to the scsi_cmd structure to be populated. + */ +static void scsi_setup_test_unit_ready(struct scsi_cmd *pccb) +{ + pccb->cmd[0] = SCSI_TST_U_RDY; + pccb->cmd[1] = 0; + pccb->cmd[2] = 0; + pccb->cmd[3] = 0; + pccb->cmd[4] = 0; + pccb->cmd[5] = 0; + pccb->cmdlen = 6; +} + +/** + * @brief Frame the "READ_10" or "READ_16" SCSI command. + * + * This function selects either a "READ_10" or "READ_16" command based on + * the number of blocks and starting address, and populates the command + * block accordingly. + * + * @param pccb: Pointer to the scsi_cmd structure to be populated. + * @param start: Starting block number. + * @param blocks: Number of blocks to read. + */ +static void scsi_setup_read(struct scsi_cmd *pccb, uint64_t start, uint32_t blocks) +{ + if ((blocks > (uint32_t)UINT16_MAX) || (start > (uint64_t)UINT32_MAX)) { + /* Read(16) for larger addresses (64-bit) and block counts (32-bit) */ + pccb->cmd[0] = SCSI_READ16; + pccb->cmd[1] = 0; + + /* start address (64-bit) */ + pccb->cmd[2] = (uint8_t)(start >> 56) & (uint8_t)(0xff); + pccb->cmd[3] = (uint8_t)(start >> 48) & (uint8_t)(0xff); + pccb->cmd[4] = (uint8_t)(start >> 40) & (uint8_t)(0xff); + pccb->cmd[5] = (uint8_t)(start >> 32) & (uint8_t)(0xff); + pccb->cmd[6] = (uint8_t)(start >> 24) & (uint8_t)(0xff); + pccb->cmd[7] = (uint8_t)(start >> 16) & (uint8_t)(0xff); + pccb->cmd[8] = (uint8_t)(start >> 8) & (uint8_t)(0xff); + pccb->cmd[9] = (uint8_t)start & (uint8_t)(0xff); + pccb->cmd[10] = 0; + + /* block count (32-bit) */ + pccb->cmd[11] = (uint8_t)(blocks >> 24) & (uint8_t)(0xff); + pccb->cmd[12] = (uint8_t)(blocks >> 16) & (uint8_t)(0xff); + pccb->cmd[13] = (uint8_t)(blocks >> 8) & (uint8_t)(0xff); + pccb->cmd[14] = (uint8_t)blocks & (uint8_t)(0xff); + pccb->cmd[15] = 0; + pccb->cmdlen = 16U; + } else { + /* Read(10) command (for smaller addresses and block counts) */ + pccb->cmd[0] = SCSI_READ10; + pccb->cmd[1] = 0; + + /* start address (32-bit) */ + pccb->cmd[2] = (uint8_t)(start >> 24) & (uint8_t)(0xff); + pccb->cmd[3] = (uint8_t)(start >> 16) & (uint8_t)(0xff); + pccb->cmd[4] = (uint8_t)(start >> 8) & (uint8_t)(0xff); + pccb->cmd[5] = (uint8_t)start & (uint8_t)(0xff); + pccb->cmd[6] = 0; + + /* block count (16-bit) */ + pccb->cmd[7] = (uint8_t)(blocks >> 8) & (uint8_t)(0xff); + pccb->cmd[8] = (uint8_t)blocks & (uint8_t)(0xff); + pccb->cmd[9] = 0; + pccb->cmdlen = 10U; + } +} + +/** + * @brief Frame the "WRITE_10" or "WRITE_16" SCSI command. + * + * This function selects either a "WRITE_10" or "WRITE_16" command based on + * the number of blocks and starting address, and populates the command + * block accordingly. + * + * @param pccb Pointer to the scsi_cmd structure to be populated. + * @param start Starting block number. + * @param blocks Number of blocks to write. + */ +static void scsi_setup_write(struct scsi_cmd *pccb, uint64_t start, uint32_t blocks) +{ + if ((blocks > (uint32_t)UINT16_MAX) || (start > (uint64_t)UINT32_MAX)) { + /* Write(16) for larger addresses (64-bit) and block counts (32-bit) */ + pccb->cmd[0] = SCSI_WRITE16; + pccb->cmd[1] = 0; + + /* start address (64-bit) */ + pccb->cmd[2] = (uint8_t)(start >> 56) & (uint8_t)(0xff); + pccb->cmd[3] = (uint8_t)(start >> 48) & (uint8_t)(0xff); + pccb->cmd[4] = (uint8_t)(start >> 40) & (uint8_t)(0xff); + pccb->cmd[5] = (uint8_t)(start >> 32) & (uint8_t)(0xff); + pccb->cmd[6] = (uint8_t)(start >> 24) & (uint8_t)(0xff); + pccb->cmd[7] = (uint8_t)(start >> 16) & (uint8_t)(0xff); + pccb->cmd[8] = (uint8_t)(start >> 8) & (uint8_t)(0xff); + pccb->cmd[9] = (uint8_t)(start) & (uint8_t)(0xff); + pccb->cmd[10] = 0; + + /* block count (32-bit) */ + pccb->cmd[11] = (uint8_t)(blocks >> 24) & (uint8_t)(0xff); + pccb->cmd[12] = (uint8_t)(blocks >> 16) & (uint8_t)(0xff); + pccb->cmd[13] = (uint8_t)(blocks >> 8) & (uint8_t)(0xff); + pccb->cmd[14] = (uint8_t)(blocks) & (uint8_t)(0xff); + pccb->cmd[15] = 0; + pccb->cmdlen = 16U; + } else { + /* Write(10) command (for smaller addresses and block counts) */ + pccb->cmd[0] = SCSI_WRITE10; + pccb->cmd[1] = 0; + + /* start address (32-bit) */ + pccb->cmd[2] = (uint8_t)(start >> 24) & (uint8_t)(0xff); + pccb->cmd[3] = (uint8_t)(start >> 16) & (uint8_t)(0xff); + pccb->cmd[4] = (uint8_t)(start >> 8) & (uint8_t)(0xff); + pccb->cmd[5] = (uint8_t)(start) & (uint8_t)(0xff); + pccb->cmd[6] = 0; + + /* block count (16-bit) */ + pccb->cmd[7] = (uint8_t)(blocks >> 8) & (uint8_t)(0xff); + pccb->cmd[8] = (uint8_t)(blocks) & (uint8_t)(0xff); + pccb->cmd[9] = 0; + pccb->cmdlen = 10U; + } +} + +/** + * @brief Test if the SCSI unit is ready. + * + * This function sends a "Test Unit Ready" (TUR) command to the device. + * + * @param scsi_dev Pointer to the scsi_device structure. + * @param lun Logical Unit Number (LUN). + * + * @return 0 if successful, or a negative error code on failure. + */ +static int scsi_test_unit_ready(struct scsi_device *scsi_dev, int32_t lun) +{ + int ret; + int32_t retries = SCSI_MAX_RETRIES; + struct scsi_cmd cmd = {0}; + + /* Set up the SCSI command structure for TUR */ + cmd.lun = (uint8_t)lun; + cmd.datalen = (0); + cmd.dma_dir = (uint8_t)PERIPHERAL_TO_PERIPHERAL; + + /* Setup the "Test Unit Ready" command */ + scsi_setup_test_unit_ready(&cmd); + + /* Execute the SCSI command and return the result */ + do { + ret = scsi_exec(scsi_dev, &cmd); + retries--; + } while ((ret < 0) && (retries > 0)); + + return ret; +} + +/** + * @brief Perform a write operation on the SCSI device. + * + * This function sends a write command to the SCSI device. + * + * @param scsi_dev Pointer to the scsi_device structure. + * @param sector Starting sector number. + * @param count Number of sectors to write. + * @param buf Pointer to the data buffer to write. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_write(struct scsi_device *scsi_dev, uint64_t sector, + uint32_t count, const uint8_t *buf) +{ + int ret; + struct scsi_cmd cmd = {0}; + + /* Set up the SCSI command structure */ + cmd.lun = scsi_dev->lun; + cmd.pdata = (uint8_t *)(uintptr_t)buf; + cmd.datalen = ((uint64_t)(scsi_dev->sector_size) * (uint64_t)(count)); + cmd.dma_dir = (uint8_t)MEMORY_TO_PERIPHERAL; + + /* Setup the "Write" command with specified sector and count */ + scsi_setup_write(&cmd, sector, count); + + /* Execute the SCSI command and return the result */ + ret = scsi_exec(scsi_dev, &cmd); + + return ret; +} + +/** + * @brief Perform a read operation on the SCSI device. + * + * This function sends a read command to the SCSI device. + * + * @param scsi_dev Pointer to the scsi_device structure. + * @param sector Starting sector number. + * @param count Number of sectors to read. + * @param buf Pointer to the buffer to store the read data. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_read(struct scsi_device *scsi_dev, uint64_t sector, uint32_t count, uint8_t *buf) +{ + int ret; + struct scsi_cmd cmd = {0}; + + /* Set up the SCSI command structure */ + cmd.lun = scsi_dev->lun; + cmd.pdata = (uint8_t *)buf; + cmd.datalen = ((uint64_t)(scsi_dev->sector_size) * (uint64_t)(count)); + cmd.dma_dir = (uint8_t)PERIPHERAL_TO_MEMORY; + + /* Setup the "Read" command with specified sector and count */ + scsi_setup_read(&cmd, sector, count); + + /* Execute the SCSI command and return the result */ + ret = scsi_exec(scsi_dev, &cmd); + + return ret; +} + +/** + * @brief Handle SG_IO ioctl for SCSI device. + * + * This function handles the SG_IO ioctl command for the SCSI device, + * which allows for sending arbitrary SCSI commands. + * + * @param sdev Pointer to the scsi_device structure. + * @param req Pointer to the ioctl arguments. + * + * @return 0 if successful, or a negative error code on failure. + */ +static int scsi_ioctl_sg_io(struct scsi_device *sdev, struct sg_io_req *req) +{ + struct scsi_cmd scmd = {0}; + int ret; + + /* Validate the request */ + if ((req == NULL) || (req->protocol != (uint32_t)BSG_PROTOCOL_SCSI) || + (req->subprotocol != (uint32_t)BSG_SUB_PROTOCOL_SCSI_CMD)) { + ret = -EINVAL; + goto out; + } + + /* Initialize the SCSI command structure with ioctl request */ + scmd.lun = sdev->lun; + scmd.cmdlen = (uint8_t)(req->request_len); + if (scmd.cmdlen > sizeof(scmd.cmd)) { + ret = -EINVAL; + goto out; + } + (void)memcpy((void *)&scmd.cmd[0], req->request, scmd.cmdlen); + scmd.dma_dir = (uint8_t)(req->dxfer_dir); + scmd.datalen = req->dxfer_len; + scmd.pdata = req->dxferp; + + /* Execute the SCSI command and return the result */ + ret = scsi_exec(sdev, &scmd); + +out: + return ret; +} + +/** + * @brief Dispatch an ioctl to a SCSI device. + * + * This function dispatches the appropriate ioctl to the SCSI device based on + * the provided command. + * + * @param sdev Pointer to the scsi_device structure. + * @param cmd The ioctl command. + * @param arg Data associated with the ioctl. + * + * @return 0 if successful, or a negative error code on failure. + */ +int scsi_ioctl(struct scsi_device *sdev, int32_t cmd, void *arg) +{ + int ret = -EINVAL; + + /* Process the specific IOCTL command */ + switch (cmd) { + case SCSI_IOCTL_TEST_UNIT_READY: + /* Check if the device is ready */ + ret = scsi_test_unit_ready(sdev, (int32_t)sdev->lun); + break; + case SG_IO: + /* Process a SCSI command via SG_IO */ + ret = scsi_ioctl_sg_io(sdev, (struct sg_io_req *)arg); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} From 4067c34a036d641a7b2070312ffbd9ce63e18e22 Mon Sep 17 00:00:00 2001 From: Ajay Neeli Date: Mon, 17 Mar 2025 12:23:34 +0530 Subject: [PATCH 02/10] ufs: Add Initial Support for UFS Subsystem Introduce initial support for the UFS subsystem in Zephyr, providing core infrastructure to enable UFS device functionality. This lays the foundation for further enhancements to the UFS subsystem in Zephyr. Key features introduced in this patch: - UFS controller initialization support. - Handling of SCSI command transfer requests. - UFS device configuration support. - Interrupt-driven UFS operations. Limitations: - Does not support task management functionality. - Does not include RPMB (Replay Protected Memory Block) support. - Asynchronous transfers are not supported. Signed-off-by: Ajay Neeli --- include/zephyr/drivers/ufshc/ufshc.h | 126 ++ include/zephyr/ufs/ufs.h | 554 +++++++ include/zephyr/ufs/ufs_ops.h | 112 ++ include/zephyr/ufs/unipro.h | 30 + subsys/CMakeLists.txt | 1 + subsys/Kconfig | 1 + subsys/ufs/CMakeLists.txt | 10 + subsys/ufs/Kconfig | 26 + subsys/ufs/ufs.c | 2211 ++++++++++++++++++++++++++ subsys/ufs/ufs_ops.c | 126 ++ 10 files changed, 3197 insertions(+) create mode 100644 include/zephyr/drivers/ufshc/ufshc.h create mode 100644 include/zephyr/ufs/ufs.h create mode 100644 include/zephyr/ufs/ufs_ops.h create mode 100644 include/zephyr/ufs/unipro.h create mode 100644 subsys/ufs/CMakeLists.txt create mode 100644 subsys/ufs/Kconfig create mode 100644 subsys/ufs/ufs.c create mode 100644 subsys/ufs/ufs_ops.c diff --git a/include/zephyr/drivers/ufshc/ufshc.h b/include/zephyr/drivers/ufshc/ufshc.h new file mode 100644 index 000000000000..40f251968991 --- /dev/null +++ b/include/zephyr/drivers/ufshc/ufshc.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHY_INCLUDE_DRIVERS_UFS_UFSHC_H_ +#define ZEPHY_INCLUDE_DRIVERS_UFS_UFSHC_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief UFSHC Driver APIs + * @defgroup ufshc_interface UFSHC Driver APIs + * @ingroup io_interfaces + * @{ + */ + +/** + * @brief UFS Host Controller (UFSHC) Driver API interface + * + * This structure defines the set of functions that must be implemented by the + * UFS Host Controller driver for PHY initialization and link startup notifications. + */ +__subsystem struct ufshc_driver_api { + /** + * @brief PHY initialization for the UFS Host Controller. + * + * This function is called to initialize the PHY layer of the UFS Host Controller. + * + * @param dev The device pointer to the UFS Host Controller device. + * + * @return 0 on success, negative errno value on failure. + * -ETIMEDOUT: command timed out during execution. + * -ENOTSUP: host controller does not support this operation. + * -EIO: I/O error. + */ + int (*phy_initialization)(const struct device *dev); + + /** + * @brief Link startup notification for UFS Host Controller. + * + * This function notifies the host controller about the link startup status. + * + * @param dev The device pointer to the UFS Host Controller device. + * @param status The status of the link startup, either PRE or POST. + * + * @return 0 on success, negative errno value on failure. + * -ETIMEDOUT: command timed out during execution. + * -ENOTSUP: host controller does not support this operation. + * -EIO: I/O error. + */ + int (*link_startup_notify)(const struct device *dev, uint8_t status); +}; + +/** + * @brief PHY initialization function for UFS Host Controller. + * + * This system call is used to initialize the PHY layer of the UFS Host Controller. + * It interacts with the driver through the `ufshc_driver_api` interface. + * + * @param dev The device pointer to the UFS Host Controller device. + * + * @return 0 on success, negative errno value on failure. + * -ETIMEDOUT: command timed out during execution. + * -ENOTSUP: host controller does not support this operation. + * -EIO: I/O error. + */ +__syscall int ufshc_variant_phy_initialization(const struct device *dev); + +static inline int z_impl_ufshc_variant_phy_initialization(const struct device *dev) +{ + const struct ufshc_driver_api *api = (const struct ufshc_driver_api *)dev->api; + + if (api->phy_initialization == NULL) { + return -ENOSYS; + } + + return api->phy_initialization(dev); +} + +/** + * @brief Link startup notification function for UFS Host Controller. + * + * This system call is used to notify the UFS Host Controller about the status of + * the link startup (PRE or POST). + * + * @param dev The device pointer to the UFS Host Controller device. + * @param status The status of the link startup, either PRE or POST. + * + * @return 0 on success, negative errno value on failure. + * -ETIMEDOUT: command timed out during execution. + * -ENOTSUP: host controller does not support this operation. + * -EIO: I/O error. + */ +__syscall int ufshc_variant_link_startup_notify(const struct device *dev, + uint8_t status); + +static inline int z_impl_ufshc_variant_link_startup_notify(const struct device *dev, + uint8_t status) +{ + const struct ufshc_driver_api *api = (const struct ufshc_driver_api *)dev->api; + + if (api->link_startup_notify == NULL) { + return -ENOSYS; + } + + return api->link_startup_notify(dev, status); +} + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#include + +#endif /* ZEPHY_INCLUDE_DRIVERS_UFS_UFSHC_H_ */ diff --git a/include/zephyr/ufs/ufs.h b/include/zephyr/ufs/ufs.h new file mode 100644 index 000000000000..498657704df6 --- /dev/null +++ b/include/zephyr/ufs/ufs.h @@ -0,0 +1,554 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_UFS_UFS_H_ +#define ZEPHYR_INCLUDE_UFS_UFS_H_ + +#include +#include +#include + +/* MACROS */ + +/* Maximum size for UFS Host Controller Query Descriptor */ +#define UFSHC_QRY_DESC_MAX_SIZE (255U) + +/* Maximum number of Physical Region Descriptor Table entries */ +#define UFSHC_PRDT_ENTRIES (128U) + +/* Maximum Logical Unit Numbers supported by UFS controller */ +#define UFSHC_MAX_LUNS (32U) + +/* Maximum length of Command Descriptor Block */ +#define UFSHC_MAX_CDB_LEN (16U) + +/* Maximum number of link-startup retries */ +#define UFSHC_DME_LINKSTARTUP_RETRIES (3) + +/* Timeout in microseconds used in UFS operations */ +#define UFS_TIMEOUT_US (1000000U) + +/* Host Controller Enable Register (0x34h) */ +#define UFSHC_HCE_MASK (0x1U) + +/* Host Controller Status Register (0x30h) */ +#define UFSHC_HCS_DP_MASK (0x1U) +#define UFSHC_HCS_UTRLRDY_MASK (0x2U) +#define UFSHC_HCS_UCRDY_MASK (0x8U) +#define UFSHC_HCS_UPMCRS_MASK (0x700U) +#define UFSHC_HCS_CCS_MASK (0x800U) + +#define UFSHCD_STATUS_READY (UFSHC_HCS_UTRLRDY_MASK | UFSHC_HCS_UCRDY_MASK) + +/* Interrupt Status Register (0x20h) */ +#define UFSHC_IS_UTRCS_MASK (0x1U) +#define UFSHC_IS_UE_MASK (0x4U) +#define UFSHC_IS_PWR_STS_MASK (0x10U) +#define UFSHC_IS_ULSS_MASK (0x100U) +#define UFSHC_IS_UCCS_MASK (0x400U) + +#define UFSHCD_UIC_MASK (UFSHC_IS_UCCS_MASK | UFSHC_IS_PWR_STS_MASK) + +#define UFSHCD_ERROR_MASK (UFSHC_IS_UE_MASK) + +#define UFSHCD_ENABLE_INTRS (UFSHC_IS_UTRCS_MASK | UFSHCD_ERROR_MASK) + +#define UFSHC_UCMDARG2_RESCODE_MASK GENMASK(7, 0) + +/* UTP Transfer Request Run-Stop Register (0x60h) */ +#define UFSHC_UTRL_RUN (0x1U) + +#define UFSHC_256KB (0x40000U) + +/* Events for UFS interrupt */ +#define UFS_UIC_CMD_COMPLETION_EVENT BIT(0) +#define UFS_UPIU_COMPLETION_EVENT BIT(1) +#define UFS_UIC_PWR_COMPLETION_EVENT BIT(2) + +/* UFS Host Controller Registers */ +#define UFSHC_HOST_CTRL_CAP 0x00U +#define UFSHC_IS 0x20U +#define UFSHC_IE 0x24U +#define UFSHC_HCS 0x30U +#define UFSHC_HCE 0x34U +#define UFSHC_UTRLBA 0x50U +#define UFSHC_UTRLBAU 0x54U +#define UFSHC_UTRLDBR 0x58U +#define UFSHC_UTRLRSR 0x60U +#define UFSHC_UICCMD 0x90U +#define UFSHC_UCMDARG1 0x94U +#define UFSHC_UCMDARG2 0x98U +#define UFSHC_UCMDARG3 0x9CU + +/* Controller capability masks */ +#define UFSHC_TRANSFER_REQ_SLOT_MASK GENMASK(4, 0) + +/* DME Operations supported */ +#define UFSHC_DME_GET_OPCODE 0x01U +#define UFSHC_DME_SET_OPCODE 0x02U +#define UFSHC_DME_LINKSTARTUP_OPCODE 0x16U + +/* Transaction Codes */ +#define UFSHC_NOP_UPIU_TRANS_CODE 0x00U +#define UFSHC_CMD_UPIU_TRANS_CODE 0x01U +#define UFSHC_TSK_UPIU_TRANS_CODE 0x04U +#define UFSHC_QRY_UPIU_TRANS_CODE 0x16U + +/* UPIU Read/Write flags */ +#define UFSHC_UPIU_FLAGS_WRITE 0x20U +#define UFSHC_UPIU_FLAGS_READ 0x40U + +/* Query function */ +#define UFSHC_QRY_READ 0x01U +#define UFSHC_QRY_WRITE 0x81U + +/* Flag IDN for Query Requests*/ +#define UFSHC_FDEVINIT_FLAG_IDN 0x01U + +/* Attribute IDN for Query Requests */ +#define UFSHC_BLUEN_ATTRID 0x00U + +/* Descriptor IDN for Query Requests */ +#define UFSHC_DEVICE_DESC_IDN 0x0U +#define UFSHC_CONFIG_DESC_IDN 0x1U +#define UFSHC_UNIT_DESC_IDN 0x2U +#define UFSHC_GEOMETRY_DESC_IDN 0x7U + +/* Unit Descriptor Parameters Offsets (bytes) */ +#define UFSHC_UD_PARAM_UNIT_INDEX 0x2U +#define UFSHC_UD_PARAM_LU_ENABLE 0x3U +#define UFSHC_UD_PARAM_LOGICAL_BLKSZ 0xAU +#define UFSHC_UD_PARAM_LOGICAL_BLKCNT 0xBU + +/* Geometry Descriptor Parameters Offsets (bytes) */ +#define UFSHC_GEO_DESC_PARAM_MAX_NUM_LUN 0xCU + +/* UTP QUERY Transaction Specific Fields OpCode */ +#define UFSHC_QRY_READ_DESC_CMD 0x1U +#define UFSHC_QRY_WRITE_DESC_CMD 0x2U +#define UFSHC_QRY_READ_ATTR_CMD 0x3U +#define UFSHC_QRY_WRITE_ATTR_CMD 0x4U +#define UFSHC_QRY_READ_FLAG_CMD 0x5U +#define UFSHC_QRY_SET_FLAG_CMD 0x6U +#define UFSHC_QRY_CLR_FLAG_CMD 0x7U + +/* UTP Transfer Request Descriptor definitions */ +#define UFSHC_CT_UFS_STORAGE_MASK 0x10000000U +#define UFSHC_INTERRUPT_CMD_MASK 0x01000000U +#define UFSHC_DD_DEV_TO_MEM_MASK 0x04000000U +#define UFSHC_DD_MEM_TO_DEV_MASK 0x02000000U + +/* Speed Gears */ + +/* PWM */ +#define UFSHC_PWM_G1 0x2201U +#define UFSHC_PWM_G2 0x2202U +#define UFSHC_PWM_G3 0x2203U +#define UFSHC_PWM_G4 0x2204U + +/* HS RATE-A */ +#define UFSHC_HS_G1 0x11101U +#define UFSHC_HS_G2 0x11102U +#define UFSHC_HS_G3 0x11103U +#define UFSHC_HS_G4 0x11104U + +/* HS RATE-B */ +#define UFSHC_HS_G1_B 0x21101U +#define UFSHC_HS_G2_B 0x21102U +#define UFSHC_HS_G3_B 0x21103U +#define UFSHC_HS_G4_B 0x21104U + +#define UFSHC_TX_RX_FAST 0x11U +#define UFSHC_TX_RX_SLOW 0x22U + +#define UFSHC_HSSERIES_A 1U +#define UFSHC_HSSERIES_B 2U + +#define UFSHC_GEAR4 4U + +/* Power Mode change request status */ +#define UFSHC_PWR_OK 0U +#define UFSHC_PWR_LOCAL 1U +#define UFSHC_PWR_ERR_CAP 4U +#define UFSHC_PWR_FATAL_ERR 5U + +#define UFSHC_PWR_MODE_VAL 0x100U + +/* ENUMS */ +enum ChangeStage { + PRE_CHANGE, + POST_CHANGE, +}; + +/* Overall command status values */ +enum CommandStatus { + OCS_SUCCESS = 0x0, + OCS_INVALID_CMD_TABLE_ATTR = 0x1, + OCS_INVALID_PRDT_ATTR = 0x2, + OCS_MISMATCH_DATA_BUF_SIZE = 0x3, + OCS_MISMATCH_RESP_UPIU_SIZE = 0x4, + OCS_PEER_COMM_FAILURE = 0x5, + OCS_ABORTED = 0x6, + OCS_FATAL_ERROR = 0x7, + OCS_DEVICE_FATAL_ERROR = 0x8, + OCS_INVALID_CRYPTO_CONFIG = 0x9, + OCS_GENERAL_CRYPTO_ERROR = 0xA, + OCS_INVALID_COMMAND_STATUS = 0x0F, +}; + +/* STRUCTURES */ + +/** + * @brief UIC Command Structure + * + * This structure represents the UIC (Unified Host Controller Interface) command + * that is used for various operations in the UFS Host Controller. + */ +struct ufshc_uic_cmd { + uint8_t command; /**< Command identifier. */ + uint8_t attr_set_type; /**< Attribute set type. */ + uint8_t reset_level; /**< Reset level. */ + uint8_t result_code; /**< Result of the command execution. */ + uint32_t mib_value; /**< MIB (Management Information Base) value. */ + uint32_t gen_sel_index; /**< Generic selector index. */ + uint16_t mib_attribute; /**< MIB attribute. */ +} __packed; + +/** + * @brief Physical Region Descriptor Table + * + * This structure describes a physical region descriptor used in data transfer + * operations. It contains the address and byte count of a data block being + * transferred, aligned to an 8-byte boundary. + */ +struct ufshc_xfer_prdt { + uint32_t bufaddr_lower; /**< Lower 32 bits of the physical address of the data block. */ + uint32_t bufaddr_upper; /**< Upper 32 bits of the physical address of the data block. */ + uint32_t reserved; /**< Reserved for future use. */ + uint32_t data_bytecount; /**< Byte count of the data block. */ +} __packed; + +/** + * @brief UTP Transfer Request Descriptor Structure + */ +struct ufshc_xfer_req_desc { + uint32_t dw0_config; /**< Configuration bits for UTP transfer. */ + uint32_t dw1_dunl; /**< Lower 32 bits of Data Unit Number (DUN). */ + uint32_t dw2_ocs; /**< Overall Command Status (OCS). */ + uint32_t dw3_dunu; /**< Upper 32 bits of Data Unit Number (DUN). */ + uint32_t dw4_utp_cmd_desc_base_addr_lo; /**< Lower 32 bits of the UTP Command Desc base. */ + uint32_t dw5_utp_cmd_desc_base_addr_up; /**< Upper 32 bits of the UTP Command Desc base. */ + uint32_t dw6_resp_upiu_info; /**< [31:16] - Resp UPIU Offset, [15:0] - Resp UPIU Length */ + uint32_t dw7_prdt_info; /**< [31:16] - PRDT Offset, [15:0] - PRDT Length */ +} __packed; + +/** + * @brief UPIU header structure + */ +struct ufshc_upiu_header { + uint8_t transaction_type; /**< Type of the transaction (e.g., command, response) */ + uint8_t flags; /**< Various flags (e.g., task management, response expected) */ + uint8_t lun; /**< Logical Unit Number (LUN) */ + uint8_t task_tag; /**< Task tag associated with the UPIU */ + uint8_t iid_cmd_type; /**< Initiator Command Type */ + uint8_t query_task_mang_fn; /**< Task Management Function (for query UPIUs) */ + uint8_t response; /**< Response code for the transaction */ + uint8_t status; /**< Transaction status (e.g., success, failure) */ + uint8_t total_ehs_len; /**< Length of the Extended Header Segment (EHS) */ + uint8_t device_info; /**< Device-specific information */ + uint16_t data_segment_len; /**< Length of the data segment (in Big Endian format) */ +} __packed; + +/** + * @brief Command UPIU Structure + */ +struct ufshc_cmd_upiu { + uint32_t exp_data_xfer_len; /**< Expected data transfer length for the command */ + uint8_t scsi_cdb[UFSHC_MAX_CDB_LEN]; /**< SCSI Command Descriptor Block (CDB) */ +} __packed; + +/** + * @brief Transaction Specific Fields Structure + */ +struct ufshc_trans_spec_flds { + uint8_t opcode; /**< Operation code for the transaction */ + uint8_t desc_id; /**< Descriptor ID */ + uint8_t index; /**< Index for the specific transaction */ + uint8_t selector; /**< Selector value */ + uint16_t reserved0; /**< Reserved field for padding or future use */ + uint16_t length; /**< Length of the transaction (Big Endian format) */ + uint32_t value; /**< Value associated with the transaction (Big Endian format) */ + uint32_t reserved1; /**< Reserved field for padding or future use */ +} __packed; + +/** + * @brief Query UPIU Structure + */ +struct ufshc_query_upiu { + struct ufshc_trans_spec_flds tsf; /**< Transaction specific fields for the query */ + uint32_t reserved; /**< Reserved field for future use */ + uint8_t data[256]; /**< Query-specific data */ +} __packed; + +/** + * @brief NOP IN/OUT UPIU Structure + */ +struct ufshc_nop_upiu { + uint8_t reserved[20]; /**< Reserved for future use */ +} __packed; + +/** + * @brief UTP Transfer Request UPIU Structure + */ +struct ufshc_xfer_req_upiu { + struct ufshc_upiu_header upiu_header; /**< UPIU header structure */ + union { + struct ufshc_cmd_upiu cmd_upiu; /**< Command UPIU structure */ + struct ufshc_query_upiu query_req_upiu; /**< Query UPIU structure */ + struct ufshc_nop_upiu nop_out_upiu; /**< NOP OUT UPIU structure */ + }; +} __packed; + +/** + * @brief Response UPIU Structure + */ +struct ufshc_resp_upiu { + uint32_t residual_trans_count; /**< Residual transfer count (Big Endian format) */ + uint32_t reserved[4]; /**< Reserved fields */ + uint16_t sense_data_len; /**< Sense data length (Big Endian format) */ + uint8_t sense_data[20]; /**< Sense data (if any) */ +} __packed; + +/** + * @brief UTP Transfer Response UPIU Structure + */ +struct ufshc_xfer_resp_upiu { + struct ufshc_upiu_header upiu_header; /**< UPIU header structure */ + union { + struct ufshc_resp_upiu resp_upiu; /**< Response UPIU structure */ + struct ufshc_query_upiu query_resp_upiu; /**< Query response UPIU structure */ + struct ufshc_nop_upiu nop_in_upiu; /**< NOP IN UPIU structure */ + }; +} __packed; + +/** + * @brief UTP Command Request Descriptor Structure + */ +struct ufshc_xfer_cmd_desc { + struct ufshc_xfer_req_upiu req_upiu; + /**< UTP Transfer Request UPIU */ + struct ufshc_xfer_resp_upiu resp_upiu __aligned(8); + /**< UTP Transfer Response UPIU (aligned to 8 bytes) */ + struct ufshc_xfer_prdt prdt[UFSHC_PRDT_ENTRIES] __aligned(8); + /**< PRDT (Physical Region Descriptor Table) (aligned to 8 bytes) */ +} __packed; + +/** + * @brief UFS Device Information Structure + */ +struct ufs_dev_info { + uint8_t max_lu_supported; /**< Maximum number of Logical Units supported */ + struct lun_info lun[UFSHC_MAX_LUNS]; /**< Array of LUN information structures */ +}; + +/** + * @brief UFS host controller private structure + */ +struct ufs_host_controller { + struct device *dev; /**< Pointer to the driver device handle. */ + mem_addr_t mmio_base; /**< Base address for the UFSHCI memory-mapped I/O registers. */ + bool is_initialized; /**< Flag indicating if the host controller has been initialized. */ + uint32_t irq; /**< IRQ number for the UFS host controller. */ + uint8_t is_cache_coherent; /**< UFS controller supports cache coherency. */ + struct ufshc_xfer_cmd_desc *ucdl_base_addr; /**< UFS Command Desc base address */ + struct ufshc_xfer_req_desc *utrdl_base_addr; /**< UTP Transfer Request Desc base address */ + uint32_t xfer_req_depth; /**< Maximum depth of the Transfer Request Queue supported */ + uint32_t outstanding_xfer_reqs; /**< Outstanding transfer requests. */ + struct k_event irq_event; /**< Event object used to signal interrupt handling. */ + struct k_mutex ufs_lock; /**< Mutex to synchronize access to UFS driver resources. */ + struct ufs_dev_info dev_info; /**< Structure holding information about the UFS device. */ + struct scsi_host_info *host; /**< SCSI host associated with the UFS controller. */ +}; + +/* INLINE FUNCTIONS */ + +/** + * @brief Write a 32-bit value to a register in UFS host controller + * + * This function writes a 32-bit data to a specific register in the UFS host controller's + * memory-mapped I/O space. + * + * @param ufshc Pointer to the UFS host controller structure + * @param reg_offset Offset from the base address of the register to write to + * @param data Data to be written to the register + */ +static inline void ufshc_write_reg(const struct ufs_host_controller *ufshc, + uintptr_t reg_offset, + uint32_t data) +{ + sys_write32((uint32_t)(data), ((uintptr_t)((ufshc)->mmio_base) + (uintptr_t)(reg_offset))); +} + +/** + * @brief Read a 32-bit value from a register in UFS host controller + * + * This function reads a 32-bit value from a specific register in the UFS host controller's + * memory-mapped I/O space. + * + * @param ufshc Pointer to the UFS host controller structure + * @param reg_offset Offset from the base address of the register to read from + * + * @return 32-bit value read from the specified register in the UFS host controller + */ +static inline uint32_t ufshc_read_reg(const struct ufs_host_controller *ufshc, + uintptr_t reg_offset) +{ + return sys_read32(((uintptr_t)((ufshc)->mmio_base) + (uintptr_t)(reg_offset))); +} + +/* PUBLIC FUNCTIONS */ + +/** + * @brief UFS Interface APIs + * @defgroup ufs_interface UFS Subsystem Interface + * @ingroup os_services + * @{ + */ + +/** + * @brief Initialize the UFS host controller + * + * This function is responsible for initializing the UFS driver by linking the UFS + * device structure to its corresponding host controller instance and performing + * the necessary initialization steps. + * + * @param ufshc_dev Pointer to the device structure for the UFS host controller + * @param ufshc Pointer to the UFS host controller structure that will be initialized + * + * @return 0 on success, negative error code on failure. + */ +int ufs_init(const struct device *ufshc_dev, struct ufs_host_controller **ufshc); + +/** + * @brief Fill UIC command structure + * + * This function fills the provided UIC command structure with the values + * for the command, MIB attribute, generation selection, attribute set type, + * and MIB value, based on the function arguments. + * + * @param uic_cmd_ptr Pointer to the UIC command structure to be filled + * @param mib_attr_gen_sel MIB attribute and generation selection value + * (upper 16-bits for attribute, lower 16-bits for selection) + * @param attr_set_type Attribute set type for the UIC command + * @param mib_val MIB value for the UIC command + * @param cmd UIC command value + */ +void ufshc_fill_uic_cmd(struct ufshc_uic_cmd *uic_cmd_ptr, + uint32_t mib_attr_gen_sel, uint32_t mib_val, + uint32_t attr_set_type, uint32_t cmd); + +/** + * @brief Send a UIC command to the UFS host controller + * + * This function sends a UIC (UFS Interface command) to the UFS host controller + * and waits for its completion. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param uic_cmd Pointer to the UIC command structure to be sent. + * + * @return 0 on success, negative error code on failure + */ +int ufshc_send_uic_cmd(struct ufs_host_controller *ufshc, struct ufshc_uic_cmd *uic_cmd); + +/** + * @brief Send raw UPIU commands to the UFS host controller + * + * This function executes raw UPIU commands such as NOP, Query, and SCSI commands + * by using the provided `msgcode`. The caller must fill the UPIU request content + * correctly, as no further validation is performed. The function copies the response + * to the `rsp` parameter if it's not NULL. + * + * @param ufshc Pointer to the UFS host controller structure + * @param msgcode Message code, one of UPIU Transaction Codes Initiator to Target + * @param req Pointer to the request data (raw UPIU command) + * @param rsp Pointer to the response data (raw UPIU reply) + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_exec_raw_upiu_cmd(struct ufs_host_controller *ufshc, + uint32_t msgcode, void *req, void *rsp); + +/** + * @brief Read or write descriptors in the UFS host controller + * + * This function reads or writes the specified UFS descriptor based on the operation mode. + * If writing, the contents of `param_buff` are written to the descriptor. If reading, + * the descriptor contents are copied into `param_buff` starting at `param_offset`. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the descriptors + * @param idn Descriptor identifier + * @param index Descriptor index + * @param param_offset Offset for the parameters in the descriptor + * @param param_buff Pointer to the buffer holding the parameters to read/write + * @param param_size Size of the parameter buffer + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_descriptors(struct ufs_host_controller *ufshc, bool write, + uint8_t idn, uint8_t index, uint8_t param_offset, + void *param_buff, uint8_t param_size); + +/** + * @brief Send attribute query requests to the UFS host controller + * + * This function performs a read or write operation for a specific UFS attribute + * identified by `idn`. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the attribute + * @param idn Attribute identifier + * @param data Pointer to the attribute value to read/write + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_attributes(struct ufs_host_controller *ufshc, bool write, uint8_t idn, void *data); + +/** + * @brief Read or write flags in the UFS host controller + * + * This function performs a read or write operation for a UFS flag specified by + * `idn` and `index`. Supports setting, clearing, or reading the flag value. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the flags + * @param idn Flag identifier + * @param index Flag index + * @param data Pointer to the data buffer for reading/writing the flag + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_flags(struct ufs_host_controller *ufshc, bool write, + uint8_t idn, uint8_t index, void *data); + +/** + * @brief Configure the UFS speed gear setting + * + * This function sets the UFS host controller's speed gear by configuring the tx and rx + * attributes (such as termination capabilities) + * + * @param ufshc Pointer to the UFS host controller structure + * @param speed_gear Desired speed gear setting + * + * @return 0 on success, negative error code on failure + */ +int ufshc_configure_speedgear(struct ufs_host_controller *ufshc, uint32_t speed_gear); + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_UFS_UFS_H_ */ diff --git a/include/zephyr/ufs/ufs_ops.h b/include/zephyr/ufs/ufs_ops.h new file mode 100644 index 000000000000..55592fe1c58d --- /dev/null +++ b/include/zephyr/ufs/ufs_ops.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief UFS Operations Header File + * + * This file contains the declarations and definitions for UFS operations + */ + +#ifndef ZEPHYR_INCLUDE_UFS_UFS_OPS_H_ +#define ZEPHYR_INCLUDE_UFS_UFS_OPS_H_ + +/* INCLUDES */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* UFS QUERY IOCTL macros */ + +/* UFS_QRY_IOCTL_ATTR - IOCTL request code for UFS attribute query */ +#define UFS_QRY_IOCTL_ATTR 0x51 + +/* UFS_QRY_IOCTL_FLAG - IOCTL request code for UFS flag query */ +#define UFS_QRY_IOCTL_FLAG 0x52 + +/* UFS_QRY_IOCTL_DESC - IOCTL request code for UFS descriptor query */ +#define UFS_QRY_IOCTL_DESC 0x53 + +/* ENUMS */ +/** + * @brief Message codes for SCSI Generic (SG) requests + */ +enum ufs_sg_req_msgcode { + UFS_SG_QUERY_REQ = 0x00, /**< Request for UFS query operation */ + UFS_SG_TASK_REQ = 0x01, /**< Request for UFS task operation */ +}; + +/* STRUCTURES */ + +/** + * @brief UFS attribute query IOCTL request + */ +struct ufs_qry_ioctl_attr { + uint8_t attr_id; /**< UFS attribute identifier for the query */ +}; + +/** + * @brief UFS flag query IOCTL request + */ +struct ufs_qry_ioctl_flag { + uint8_t flag_id; /**< UFS flag identifier for the query */ +}; + +/** + * @brief UFS descriptor query IOCTL request + */ +struct ufs_qry_ioctl_desc { + uint8_t desc_id; /**< Descriptor identifier for the query */ + uint8_t index; /**< Index for the descriptor query */ + uint8_t param_offset; /**< Offset for the parameter in the descriptor query */ +}; + +/** + * @brief UFS IOCTL request + * + * This structure holds data for different types of UFS query IOCTL requests, + * including attribute, flag, and descriptor queries. + */ +struct ufs_qry_ioctl_req { + uint32_t ioctl_id; /**< IOCTL identifier for the request */ + union { + struct ufs_qry_ioctl_attr attr; /**< UFS attribute query data */ + struct ufs_qry_ioctl_flag flag; /**< UFS flag query data */ + struct ufs_qry_ioctl_desc desc; /**< UFS descriptor query data */ + }; +}; + +/** + * @brief UFS SCSI Generic (SG) request + * + * This structure is used for representing SCSI Generic requests, + * querying UFS attributes, flags, or descriptors. + */ +struct ufs_sg_req { + int32_t msgcode; /**< Message code for the request */ + struct ufs_qry_ioctl_req *req_qry_ioctl; /**< Pointer to the UFS query IOCTL request data */ +}; + +/* PUBLIC FUNCTIONS */ + +/** + * @brief Handle a SCSI Generic (SG) request for UFS + * + * This function dispatches an SG_IO request to the appropriate UFS operation. + * + * @param ufshc Pointer to the UFS host controller structure + * @param arg Argument for the request, which could be a UFS query or other data + * + * @return 0 on success, negative error code on failure + */ +int ufs_sg_request(struct ufs_host_controller *ufshc, void *arg); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_UFS_UFS_OPS_H_ */ diff --git a/include/zephyr/ufs/unipro.h b/include/zephyr/ufs/unipro.h new file mode 100644 index 000000000000..86a801a5bdb7 --- /dev/null +++ b/include/zephyr/ufs/unipro.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_UFS_UNIPRO_H_ +#define ZEPHYR_INCLUDE_UFS_UNIPRO_H_ + +/* PHY Adapter Attributes */ +#define PA_ACTIVETXDATALANES 0x1560 +#define PA_CONNECTEDTXDATALANES 0x1561 +#define PA_TXGEAR 0x1568 +#define PA_TXTERMINATION 0x1569 +#define PA_HSSERIES 0x156A +#define PA_PWRMODE 0x1571 +#define PA_ACTIVERXDATALANES 0x1580 +#define PA_CONNECTEDRXDATALANES 0x1581 +#define PA_RXGEAR 0x1583 +#define PA_RXTERMINATION 0x1584 +#define PA_TXHSADAPTTYPE 0x15D4 + +/* Transport Layer Attributes */ +#define T_CONNECTIONSTATE 0x4020 + +/* Other Attributes */ +#define VS_MPHYCFGUPDT 0xD085 +#define VS_MPHYDISABLE 0xD0C1 + +#endif /* ZEPHYR_INCLUDE_UFS_UNIPRO_H_ */ diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index 3ccb53753340..8af0f9f2abfa 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -57,5 +57,6 @@ add_subdirectory_ifdef(CONFIG_SENSING sensing) add_subdirectory_ifdef(CONFIG_SETTINGS settings) add_subdirectory_ifdef(CONFIG_SHELL shell) add_subdirectory_ifdef(CONFIG_TIMING_FUNCTIONS timing) +add_subdirectory_ifdef(CONFIG_UFS_STACK ufs) add_subdirectory_ifdef(CONFIG_ZBUS zbus) # zephyr-keep-sorted-stop diff --git a/subsys/Kconfig b/subsys/Kconfig index dcf8580d2f74..f0eb6ffb3662 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -51,6 +51,7 @@ source "subsys/task_wdt/Kconfig" source "subsys/testsuite/Kconfig" source "subsys/timing/Kconfig" source "subsys/tracing/Kconfig" +source "subsys/ufs/Kconfig" source "subsys/usb/device/Kconfig" source "subsys/usb/device_next/Kconfig" source "subsys/usb/host/Kconfig" diff --git a/subsys/ufs/CMakeLists.txt b/subsys/ufs/CMakeLists.txt new file mode 100644 index 000000000000..7ae651ba08b3 --- /dev/null +++ b/subsys/ufs/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +if (CONFIG_UFS_STACK) + zephyr_interface_library_named(UFS) + + zephyr_library() + zephyr_library_sources(ufs.c ufs_ops.c) + +endif() diff --git a/subsys/ufs/Kconfig b/subsys/ufs/Kconfig new file mode 100644 index 000000000000..2ec18a5916c9 --- /dev/null +++ b/subsys/ufs/Kconfig @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +# UFS stack configuration options + +menuconfig UFS_STACK + bool "UFS protocol support" + depends on SCSI + select UFSHC + select DYNAMIC_INTERRUPTS + select EVENTS + default n + help + Enable support for the UFS (Universal Flash Storage) protocol. + This is required for the proper functioning of UFS devices. + +if UFS_STACK + +# UFS module configuration +module = UFS +module-str = UFS stack + +# Source additional logging configuration +source "subsys/logging/Kconfig.template.log_config" + +endif # UFS_STACK diff --git a/subsys/ufs/ufs.c b/subsys/ufs/ufs.c new file mode 100644 index 000000000000..b225854c2b51 --- /dev/null +++ b/subsys/ufs/ufs.c @@ -0,0 +1,2211 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This file implements core initialization + * for Universal Flash Storage Host Controller driver + * + * Right now the implementation: + * - support UFS Host Controller Initialization + * - support Device configuration + * - support Command Transfer Requests + * - does not support Task Management + * - does not support RPMB request + * - does not support asynchronous transfer + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ufs, CONFIG_UFS_LOG_LEVEL); + +/** + * @brief Fill the UFS Protocol Information Unit (UPIU) header + * + * @param ufshc Pointer to the UFS host controller instance + * @param trans_type Transaction type to be set in the UPIU header + * @param upiu_dw0 UPIU flags and task tag, combined into a 32-bit word + * @param query_task_mang_fn Task management function value to be set in the UPIU header + * @param data_segment_len Length of the data segment, in little-endian format + */ +static void ufshc_fill_upiu_header(struct ufs_host_controller *ufshc, + uint8_t trans_type, uint32_t upiu_dw0, + uint8_t query_task_mang_fn, + uint16_t data_segment_len) +{ + struct ufshc_xfer_req_upiu *ucd_req_ptr = &ufshc->ucdl_base_addr->req_upiu; + + ucd_req_ptr->upiu_header.transaction_type = trans_type; + ucd_req_ptr->upiu_header.flags = (uint8_t)(upiu_dw0 >> 8U); + ucd_req_ptr->upiu_header.task_tag = (uint8_t)(upiu_dw0 >> 24U); + ucd_req_ptr->upiu_header.query_task_mang_fn = query_task_mang_fn; + ucd_req_ptr->upiu_header.data_segment_len = sys_cpu_to_be16(data_segment_len); +} + +/** + * @brief Fill UTP Transfer request descriptor header + * + * @param ufshc Pointer to the UFS host controller instance + * @param slot Slot index to place the transfer request descriptor + * @param data_direction Data transfer direction (e.g., read or write) + * @param resp_upiu_len Length of the response UPIU (Protocol Information Unit) + * @param prdt_len Length of the PRDT (Physical Region Descriptor Table) entry, or zero if none + */ +static void ufshc_fill_utp_trans_req_desc(struct ufs_host_controller *ufshc, + uint32_t slot, uint32_t data_direction, + uint32_t resp_upiu_len, uint32_t prdt_len) +{ + uint32_t dw0; + uint32_t prdt_offset; + uint32_t resp_upiu_offset; + uint32_t resp_upiu_info; + uint32_t prdt_info; + struct ufshc_xfer_req_desc *utrd_ptr; + + /* Set the control word (dw0) with storage, data direction, and interrupt */ + dw0 = ((uint32_t)UFSHC_CT_UFS_STORAGE_MASK | + (uint32_t)data_direction | + (uint32_t)UFSHC_INTERRUPT_CMD_MASK); + + /* response UPIU offset and info */ + resp_upiu_offset = (uint32_t)((uintptr_t)&(ufshc->ucdl_base_addr->resp_upiu) - + (uintptr_t)(ufshc->ucdl_base_addr)); + resp_upiu_info = (uint32_t)((resp_upiu_offset >> 2U) << 16U) | + (uint32_t)((sizeof(ufshc->ucdl_base_addr->resp_upiu.upiu_header) + + resp_upiu_len) >> 2U); + + /* calculate PRDT offset and related information */ + if (prdt_len != 0U) { + prdt_offset = (uint32_t)((uintptr_t)&(ufshc->ucdl_base_addr->prdt) - + (uintptr_t)ufshc->ucdl_base_addr); + prdt_info = (uint32_t)((prdt_offset >> 2U) << 16U) | (uint32_t)prdt_len; + } else { + prdt_info = 0U; + } + + /* fill the transfer request descriptor for the given slot */ + utrd_ptr = ((struct ufshc_xfer_req_desc *)ufshc->utrdl_base_addr) + slot; + + utrd_ptr->dw0_config = dw0; + utrd_ptr->dw1_dunl = 0U; + utrd_ptr->dw2_ocs = (uint32_t)OCS_INVALID_COMMAND_STATUS; + utrd_ptr->dw3_dunu = 0U; + utrd_ptr->dw4_utp_cmd_desc_base_addr_lo = (uint32_t)((uintptr_t)(ufshc->ucdl_base_addr)); + utrd_ptr->dw5_utp_cmd_desc_base_addr_up = 0U; + utrd_ptr->dw6_resp_upiu_info = resp_upiu_info; + utrd_ptr->dw7_prdt_info = prdt_info; +} + +/** + * @brief Find an available slot in the transfer request list (TRL) + * + * This function checks the Transfer Request List (TRL) of the UFS host controller to find + * an available slot. + * + * @param ufshc Pointer to the UFS host controller private data structure + * @param slot Pointer to a variable that will hold the index of the available slot + */ +static void ufshc_find_slot_in_trl(struct ufs_host_controller *ufshc, uint32_t *slot) +{ + uint32_t reg_data; + uint32_t index; + + reg_data = ufshc_read_reg(ufshc, UFSHC_UTRLDBR); + + for (index = 0; index < ufshc->xfer_req_depth; index++) { + if ((reg_data & ((uint32_t)1U << index)) == 0U) { + *slot = index; + break; + } + } +} + +/** + * @brief Fill the UPIU (UFS Protocol Information Unit) for SCSI commands + * + * @param ufshc Pointer to the UFS host controller private data structure + * @param slot Slot index for the UFS transaction + * @param pccb Pointer to the SCSI command structure (SCSI Control Block) + */ +static void ufshc_fill_scsi_cmd_upiu(struct ufs_host_controller *ufshc, + uint32_t slot, struct scsi_cmd *pccb) +{ + uint32_t data_direction; + uint32_t prdt_entries; + uint32_t data_cnt; + uint8_t *localbuf; + uint32_t index = 0U; + uint32_t upiu_dw0, tmp_val; + uint32_t task_tag = slot; + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + + (void)memset(ucd_ptr, 0, sizeof(struct ufshc_xfer_cmd_desc)); + + /* data direction */ + if (pccb->dma_dir == (uint8_t)PERIPHERAL_TO_MEMORY) { + data_direction = UFSHC_DD_DEV_TO_MEM_MASK; + upiu_dw0 = ((uint32_t)UFSHC_UPIU_FLAGS_READ << 8U) | + (task_tag << 24U); + } else if (pccb->dma_dir == (uint8_t)MEMORY_TO_PERIPHERAL) { + data_direction = UFSHC_DD_MEM_TO_DEV_MASK; + upiu_dw0 = ((uint32_t)UFSHC_UPIU_FLAGS_WRITE << 8U) | + (task_tag << 24U); + } else { + data_direction = 0U; + upiu_dw0 = (slot << 24U); + } + ufshc_fill_upiu_header(ufshc, (uint8_t)UFSHC_CMD_UPIU_TRANS_CODE, + upiu_dw0, 0U, 0U); + ucd_ptr->req_upiu.upiu_header.lun = pccb->lun; + + /* fill scsi cmd upiu */ + (void)memcpy(ucd_ptr->req_upiu.cmd_upiu.scsi_cdb, pccb->cmd, pccb->cmdlen); + ucd_ptr->req_upiu.cmd_upiu.exp_data_xfer_len = sys_cpu_to_be32((uint32_t)pccb->datalen); + + /* fill prdt entries */ + data_cnt = (uint32_t)(pccb->datalen); + prdt_entries = data_cnt / UFSHC_256KB; + + /* iterate for full 256KB blocks */ + for (index = 0U; index < prdt_entries; index++) { + if (pccb->pdata != NULL) { + localbuf = &(pccb->pdata[(index * UFSHC_256KB)]); + } else { + localbuf = NULL; + } + ucd_ptr->prdt[index].bufaddr_lower = (uint32_t)((uintptr_t)localbuf); + tmp_val = (uint32_t)((uint64_t)((uintptr_t)localbuf) >> 32U); + ucd_ptr->prdt[index].bufaddr_upper = tmp_val; + ucd_ptr->prdt[index].data_bytecount = UFSHC_256KB - 1U; + } + + /* leftover (less than 256KB) */ + if ((data_cnt % UFSHC_256KB) != 0U) { + if (pccb->pdata != NULL) { + localbuf = &(pccb->pdata[(index * UFSHC_256KB)]); + } else { + localbuf = NULL; + } + ucd_ptr->prdt[index].bufaddr_lower = (uint32_t)((uintptr_t)localbuf); + tmp_val = (uint32_t)((uint64_t)((uintptr_t)localbuf) >> 32U); + ucd_ptr->prdt[index].bufaddr_upper = tmp_val; + ucd_ptr->prdt[index].data_bytecount = (data_cnt % UFSHC_256KB) - 1U; + prdt_entries = prdt_entries + 1U; + } + + /* Perform cache flush to ensure correct data transfer */ + (void)sys_cache_data_flush_range((void *)pccb->pdata, data_cnt); + (void)sys_cache_data_invd_range((void *)pccb->pdata, data_cnt); + + /* Fill the UTP (UFS Transport Protocol) request descriptor */ + ufshc_fill_utp_trans_req_desc(ufshc, slot, data_direction, + (uint32_t)(sizeof(ucd_ptr->resp_upiu.resp_upiu)), + prdt_entries); +} + +/** + * @brief Initialize the query request transaction fields for UPIU + * + * @param ufshc Pointer to the UFS host controller private data structure + * @param cmd Query opcode (command for the query) + * @param tsf_dw0 Query parameters packed into a 32-bit value (includes IDN, index, and selector) + * @param value The query value to be sent + * @param length The length of the query + */ +static void ufshc_fill_query_req_upiu_tsf(struct ufs_host_controller *ufshc, + uint8_t cmd, uint32_t tsf_dw0, + uint32_t value, uint16_t length) +{ + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + struct ufshc_xfer_req_upiu *req_upiu = &ucd_ptr->req_upiu; + + req_upiu->query_req_upiu.tsf.opcode = cmd; + req_upiu->query_req_upiu.tsf.desc_id = (uint8_t)(tsf_dw0 >> 8U); + req_upiu->query_req_upiu.tsf.index = (uint8_t)(tsf_dw0 >> 16U); + req_upiu->query_req_upiu.tsf.selector = (uint8_t)(tsf_dw0 >> 24U); + req_upiu->query_req_upiu.tsf.value = sys_cpu_to_be32(value); + req_upiu->query_req_upiu.tsf.length = sys_cpu_to_be16(length); +} + +/** + * @brief Initialize the query request parameters for UPIU + * + * @param ufshc Pointer to the UFS host controller private data structure + * @param slot The slot index for the UFS command + * @param opcode Query opcode (command for the query) + * @param idn Query descriptor ID + * @param idx Query index + * @param sel Query selector + * @param val Query value + * @param len Query length + */ +static void ufshc_fill_query_upiu(struct ufs_host_controller *ufshc, uint32_t slot, + int32_t opcode, uint8_t idn, uint8_t idx, + uint8_t sel, uint32_t val, uint16_t len) +{ + uint32_t task_tag = slot; + uint8_t query_task_mang_fn = 0U; + uint32_t upiu_dw0; + uint32_t tsf_dw0 = 0U; + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + + /* Determine the query type based on the provided opcode */ + switch (opcode) { + case (int32_t)UFSHC_QRY_READ_DESC_CMD: + case (int32_t)UFSHC_QRY_READ_ATTR_CMD: + case (int32_t)UFSHC_QRY_READ_FLAG_CMD: + query_task_mang_fn = UFSHC_QRY_READ; + break; + case (int32_t)UFSHC_QRY_WRITE_ATTR_CMD: + case (int32_t)UFSHC_QRY_WRITE_DESC_CMD: + case (int32_t)UFSHC_QRY_SET_FLAG_CMD: + query_task_mang_fn = UFSHC_QRY_WRITE; + break; + default: + query_task_mang_fn = 0; + break; + } + + /* Construct the UPIU header */ + upiu_dw0 = (task_tag << 24U); + ufshc_fill_upiu_header(ufshc, (uint8_t)UFSHC_QRY_UPIU_TRANS_CODE, + upiu_dw0, query_task_mang_fn, 0U); + + /* Construct the transaction-specific data word (TSF) */ + tsf_dw0 = ((uint32_t)idn << 8U) | + ((uint32_t)idx << 16U) | + ((uint32_t)sel << 24U); + ufshc_fill_query_req_upiu_tsf(ufshc, (uint8_t)opcode, + tsf_dw0, val, (uint16_t)len); + + /* Initialize the Response field with FAILURE */ + ucd_ptr->resp_upiu.upiu_header.response = 1U; + + /* Initialize the UTP transfer request descriptor */ + ufshc_fill_utp_trans_req_desc(ufshc, slot, 0U, + (uint32_t)(sizeof(ucd_ptr->resp_upiu.query_resp_upiu)), 0U); +} + +/** + * @brief Initialize the attribute UPIU request parameters. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param slot Slot number for the UPIU request. + * @param opcode Opcode specifying the operation to perform (e.g., read, write). + * @param idn Attribute identifier (IDN) to access. + * @param index Index field for the attribute request. + * @param selector Selector field for the attribute request. + * @param attr_val Attribute value to be used in the request. + */ +static inline void ufshc_fill_attr_upiu(struct ufs_host_controller *ufshc, uint32_t slot, + int32_t opcode, int32_t idn, uint8_t index, + uint8_t selector, uint32_t attr_val) +{ + ufshc_fill_query_upiu(ufshc, slot, opcode, + (uint8_t)idn, index, selector, + attr_val, 0U); +} + +/** + * @brief Initialize the descriptor UPIU request parameters. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param slot Slot number for the UPIU request. + * @param opcode Opcode specifying the operation to perform (e.g., read, write). + * @param idn Descriptor identifier (IDN) to access. + * @param index: Index field for the descriptor request. + * @param selector Selector field for the descriptor request. + * @param desc_len Length parameter of the descriptor to be used in the request. + */ +static inline void ufshc_fill_desc_upiu(struct ufs_host_controller *ufshc, uint32_t slot, + int32_t opcode, int32_t idn, uint8_t index, + uint8_t selector, int32_t desc_len) +{ + ufshc_fill_query_upiu(ufshc, slot, opcode, + (uint8_t)idn, index, selector, 0U, (uint16_t)desc_len); +} + +/** + * @brief Initialize the flag UPIU request parameters. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param slot Slot number for the UPIU request. + * @param opcode Opcode specifying the flag operation to perform. + * @param idn Flag identifier (IDN) to access. + * @param index Index field for the flag request. + * @param selector Selector field for the flag request. + */ +static inline void ufshc_fill_flag_upiu(struct ufs_host_controller *ufshc, uint32_t slot, + int32_t opcode, int32_t idn, + uint8_t index, uint8_t selector) +{ + ufshc_fill_query_upiu(ufshc, slot, opcode, + (uint8_t)idn, index, selector, 0U, 0U); +} + +/** + * @brief Fill NOP UPIU (No Operation) request parameters. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param slot Slot number for the UPIU request. + */ +static void ufshc_fill_nop_upiu(struct ufs_host_controller *ufshc, uint32_t slot) +{ + uint32_t upiu_dw0; + uint32_t task_tag = slot; + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + + /* Clear the command descriptor entry to ensure no residual data */ + (void)memset(ufshc->ucdl_base_addr, 0, sizeof(struct ufshc_xfer_cmd_desc)); + + /* Construct the UPIU header */ + upiu_dw0 = (task_tag << 24U); + ufshc_fill_upiu_header(ufshc, (uint8_t)UFSHC_NOP_UPIU_TRANS_CODE, upiu_dw0, 0, 0); + + /* Initialize the Response field with FAILURE */ + ucd_ptr->resp_upiu.upiu_header.response = 1U; + + /* Initialize the UTP transfer request descriptor */ + ufshc_fill_utp_trans_req_desc(ufshc, slot, 0U, + (uint32_t)(sizeof(ucd_ptr->resp_upiu.nop_in_upiu)), 0U); +} + +/** + * @brief Send a SCSI and UPIU command to the UFS host controller. + * + * This function sends a UPIU (Unified Protocol Information Unit) command + * to the UFS host controller. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param slot_tag Slot tag for the transfer request. + * + * @return 0 on success, or a negative error code on failure. + */ +static int32_t ufshc_send_upiu_cmd(struct ufs_host_controller *ufshc, uint32_t slot_tag) +{ + int32_t err = 0; + uint32_t read_reg; + uint32_t events; + k_timeout_t timeout_event; + struct ufshc_xfer_req_desc *utrd_ptr; + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + struct ufshc_xfer_req_upiu *req_upiu = &ucd_ptr->req_upiu; + struct ufshc_xfer_resp_upiu *resp_upiu = &ucd_ptr->resp_upiu; + uint8_t req_trans_type = req_upiu->upiu_header.transaction_type; + + /* Check UTP Transfer Request List Run-Stop Register */ + read_reg = ufshc_read_reg(ufshc, UFSHC_UTRLRSR); + if ((read_reg & UFSHC_UTRL_RUN) != UFSHC_UTRL_RUN) { + err = -EIO; + goto out; + } + + /* Ensure cache coherence */ + if (ufshc->is_cache_coherent == 0U) { + /* Flush the request UPIU header */ + (void)sys_cache_data_flush_range((void *)&req_upiu->upiu_header, + sizeof(req_upiu->upiu_header)); + + /* Depending on the transaction type, flush additional data structures */ + if (req_trans_type == (uint8_t)UFSHC_NOP_UPIU_TRANS_CODE) { + (void)sys_cache_data_flush_range((void *)&req_upiu->nop_out_upiu, + sizeof(req_upiu->nop_out_upiu)); + } else if (req_trans_type == (uint8_t)UFSHC_CMD_UPIU_TRANS_CODE) { + (void)sys_cache_data_flush_range((void *)&req_upiu->cmd_upiu, + sizeof(req_upiu->cmd_upiu)); + (void)sys_cache_data_flush_range((void *)&ucd_ptr->prdt, + sizeof(ucd_ptr->prdt)); + } else if (req_trans_type == (uint8_t)UFSHC_QRY_UPIU_TRANS_CODE) { + (void)sys_cache_data_flush_range((void *)&req_upiu->query_req_upiu, + sizeof(req_upiu->query_req_upiu)); + } else { + LOG_ERR("ufs-send-upiu: invalid upiu request, transaction_type: %d", + req_trans_type); + err = -EINVAL; + goto out; + } + + /* Invalidate the response UPIU header */ + (void)sys_cache_data_invd_range((void *)&resp_upiu->upiu_header, + sizeof(resp_upiu->upiu_header)); + + /* Invalidate fields depending on the request transaction type */ + if (req_trans_type == (uint8_t)UFSHC_NOP_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->nop_in_upiu, + sizeof(resp_upiu->nop_in_upiu)); + } else if (req_trans_type == (uint8_t)UFSHC_CMD_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->resp_upiu, + sizeof(resp_upiu->resp_upiu)); + } else if (req_trans_type == (uint8_t)UFSHC_QRY_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->query_resp_upiu, + sizeof(resp_upiu->query_resp_upiu)); + } else { + /* do nothing.. code is not reached */ + } + + /* Invalidate the entire UCD (Command Descriptor) to ensure it is up-to-date */ + (void)sys_cache_data_invd_range((void *)ucd_ptr, sizeof(*(ucd_ptr))); + } + + /* Clear the outstanding transfer request event */ + (void)k_event_clear(&ufshc->irq_event, + (uint32_t)UFS_UPIU_COMPLETION_EVENT); + + /* Mark this slot as having an outstanding transfer request */ + sys_set_bit((mem_addr_t)((uintptr_t)&ufshc->outstanding_xfer_reqs), slot_tag); + + /* Start the transfer */ + ufshc_write_reg(ufshc, UFSHC_UTRLDBR, ((uint32_t)1U << slot_tag)); + + /* wait for completion of the transfer */ + timeout_event = K_USEC(UFS_TIMEOUT_US); + events = k_event_wait(&ufshc->irq_event, + (uint32_t)UFS_UPIU_COMPLETION_EVENT, + false, timeout_event); + if ((events & UFS_UPIU_COMPLETION_EVENT) == 0U) { + LOG_ERR("ufs-send-upiu: cmd request timedout, tag %d", slot_tag); + err = -ETIMEDOUT; + goto out; + } + + /* Get the transfer request descriptor for this slot */ + utrd_ptr = ((struct ufshc_xfer_req_desc *)ufshc->utrdl_base_addr) + slot_tag; + + /* Ensure cache coherence */ + if (ufshc->is_cache_coherent == 0U) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->upiu_header, + sizeof(resp_upiu->upiu_header)); + if (req_trans_type == (uint8_t)UFSHC_NOP_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->nop_in_upiu, + sizeof(resp_upiu->nop_in_upiu)); + } else if (req_trans_type == (uint8_t)UFSHC_CMD_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->resp_upiu, + sizeof(resp_upiu->resp_upiu)); + } else if (req_trans_type == (uint8_t)UFSHC_QRY_UPIU_TRANS_CODE) { + (void)sys_cache_data_invd_range((void *)&resp_upiu->query_resp_upiu, + sizeof(resp_upiu->query_resp_upiu)); + } else { + /* do nothing.. code is not reached */ + } + + (void)sys_cache_data_invd_range((void *)utrd_ptr, sizeof(*(utrd_ptr))); + } + + /* Check if the transfer was successful by evaluating the status fields */ + if (utrd_ptr->dw2_ocs != 0U) { + LOG_ERR("OCS error from controller = %x for tag %d", utrd_ptr->dw2_ocs, slot_tag); + err = -EIO; + } else { + if (resp_upiu->upiu_header.response != 0U) { + LOG_ERR("ufs-send-upiu: unexpected response: %x", + resp_upiu->upiu_header.response); + err = -EIO; + } else if (resp_upiu->upiu_header.status != 0U) { + LOG_ERR("ufs-send-upiu: unexpected status:%x", + resp_upiu->upiu_header.status); + err = -EIO; + } else if (resp_upiu->upiu_header.task_tag != + req_upiu->upiu_header.task_tag) { + err = -EIO; + } else { + err = 0; /* No error, transfer successful */ + } + } + +out: + return err; +} + +/** + * @brief Send a flag query request to the UFS host controller. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param opcode The flag query operation to perform. + * @param idn The flag identifier (IDN) to access. + * @param index The flag index to access. + * @param flag_res Pointer to store the queried flag value (true or false). + * + * @return 0 on success, or a non-zero error code on failure. + */ +static int32_t ufshc_query_flag(struct ufs_host_controller *ufshc, + int32_t opcode, int32_t idn, uint8_t index, + bool *flag_res) +{ + int32_t err, selector = 0; + uint32_t tmp_be32_val; + uint32_t slot = 0U; + struct ufshc_query_upiu *query_resp_ptr; + + /* Find an available slot in the transfer request list (TRL) */ + ufshc_find_slot_in_trl(ufshc, &slot); + + /* Clear any previous command descriptor to prepare for new transfer. */ + (void)memset(ufshc->ucdl_base_addr, 0, sizeof(struct ufshc_xfer_cmd_desc)); + + /* Fill the UPIU with query details */ + ufshc_fill_flag_upiu(ufshc, slot, opcode, idn, index, (uint8_t)selector); + + /* Send the UPIU command and wait for the result */ + err = ufshc_send_upiu_cmd(ufshc, slot); + if ((err == 0) && (flag_res != NULL)) { + query_resp_ptr = &ufshc->ucdl_base_addr->resp_upiu.query_resp_upiu; + tmp_be32_val = sys_cpu_to_be32(query_resp_ptr->tsf.value); + if ((tmp_be32_val & 0x1U) != 0U) { + *flag_res = true; + } else { + *flag_res = false; + } + } + + if (err != 0) { + LOG_ERR("ufs-query-flag: Sending for idn %d failed, err = %d", idn, err); + } + + return err; +} + +/** + * @brief Send a descriptor query request to the UFS host controller. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param opcode The descriptor operation code. + * @param idn The descriptor identifier (IDN) to access. + * @param index The index field to use in the query. + * @param selector The selector field to use in the query. + * @param desc_buf Pointer to the buffer to store the retrieved descriptor data. + * @param desc_len Length of the descriptor data to be retrieved. + * + * @return 0 on success, or a non-zero error code on failure. + */ +static int32_t ufshc_query_descriptor(struct ufs_host_controller *ufshc, + int32_t opcode, int32_t idn, + uint8_t index, uint8_t selector, + uint8_t *desc_buf, int32_t desc_len) +{ + int32_t err; + uint32_t slot = 0U; + struct ufshc_xfer_cmd_desc *cmd_desc_ptr = ufshc->ucdl_base_addr; + + /* Find an available slot in the transfer request list (TRL) */ + ufshc_find_slot_in_trl(ufshc, &slot); + + /* Clear any previous command descriptor to prepare for new transfer. */ + (void)memset(ufshc->ucdl_base_addr, 0, sizeof(struct ufshc_xfer_cmd_desc)); + + /* Fill the UPIU with query details */ + ufshc_fill_desc_upiu(ufshc, slot, opcode, idn, index, selector, desc_len); + + /* Send the UPIU command and wait for the result */ + err = ufshc_send_upiu_cmd(ufshc, slot); + if ((err == 0) && (desc_buf != NULL) && + (opcode == (int32_t)UFSHC_QRY_READ_DESC_CMD)) { + (void)memcpy(desc_buf, + cmd_desc_ptr->resp_upiu.query_resp_upiu.data, + (size_t)desc_len); + } + + if (err != 0) { + LOG_ERR("ufs-query-desc: opcode 0x%02X for idn %d failed, index %d, err = %d", + opcode, idn, index, err); + } + + return err; +} + +/** + * @brief Send an attribute query request to the UFS host controller. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param opcode The attribute operation code. + * @param idn The attribute identifier (IDN) to access. + * @param index The index field to use in the query. + * @param selector The selector field to use in the query. + * @param attr_val Pointer to store the queried attribute value. + * + * @return 0 on success, or a non-zero error code on failure. + */ +static int32_t ufshc_query_attr(struct ufs_host_controller *ufshc, + int32_t opcode, int32_t idn, + uint8_t index, uint8_t selector, + uint32_t *attr_val) +{ + int32_t err; + uint32_t slot = 0U; + struct ufshc_query_upiu *query_resp_ptr; + + if (attr_val == NULL) { + return -EINVAL; + } + + /* Find an available slot in the transfer request list (TRL) */ + ufshc_find_slot_in_trl(ufshc, &slot); + + /* Clear any previous command descriptor to prepare for new transfer. */ + (void)memset(ufshc->ucdl_base_addr, 0, sizeof(struct ufshc_xfer_cmd_desc)); + + /* Fill the UPIU with query details */ + ufshc_fill_attr_upiu(ufshc, slot, opcode, idn, index, selector, *attr_val); + + /* Send the UPIU command and wait for the result */ + err = ufshc_send_upiu_cmd(ufshc, slot); + if ((err == 0) && (opcode == (int32_t)UFSHC_QRY_READ_ATTR_CMD)) { + query_resp_ptr = &ufshc->ucdl_base_addr->resp_upiu.query_resp_upiu; + *attr_val = sys_cpu_to_be32(query_resp_ptr->tsf.value); + } + + if (err != 0) { + LOG_ERR("ufs-query-attr: opcode 0x%02X for idn %d failed, index %d, err = %d", + opcode, idn, index, err); + } + + return err; +} + +/** + * @brief Fill UIC command structure + * + * This function fills the provided UIC command structure with the values + * for the command, MIB attribute, generation selection, attribute set type, + * and MIB value, based on the function arguments. + * + * @param uic_cmd_ptr Pointer to the UIC command structure to be filled + * @param mib_attr_gen_sel MIB attribute and generation selection value + * (upper 16-bits for attribute, lower 16-bits for selection) + * @param attr_set_type Attribute set type for the UIC command + * @param mib_val MIB value for the UIC command + * @param cmd UIC command value + */ +void ufshc_fill_uic_cmd(struct ufshc_uic_cmd *uic_cmd_ptr, + uint32_t mib_attr_gen_sel, uint32_t mib_val, + uint32_t attr_set_type, uint32_t cmd) +{ + uic_cmd_ptr->command = (uint8_t)cmd; + uic_cmd_ptr->mib_attribute = (uint16_t)(mib_attr_gen_sel >> 16U); + uic_cmd_ptr->gen_sel_index = (uint16_t)mib_attr_gen_sel; + uic_cmd_ptr->attr_set_type = (uint8_t)attr_set_type; + uic_cmd_ptr->mib_value = mib_val; +} + +/** + * @brief Check if the UFS host controller is ready to accept UIC commands + * + * This function checks the readiness of the UFS host controller to accept UIC + * commands by inspecting the UCRDY (UFS Controller Ready) bit in the HCS register. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return true if the controller is ready to accept UIC commands, false otherwise. + */ +static bool ufshc_ready_for_uic_cmd(struct ufs_host_controller *ufshc) +{ + if ((ufshc_read_reg(ufshc, UFSHC_HCS) & UFSHC_HCS_UCRDY_MASK) != 0U) { + return true; + } else { + return false; + } +} + +/** + * @brief Send a UIC command to the UFS host controller + * + * This function sends a UIC (UFS Interface command) to the UFS host controller + * and waits for its completion. + * + * @param ufshc Pointer to the UFS host controller instance. + * @param uic_cmd Pointer to the UIC command structure to be sent. + * + * @return 0 on success, negative error code on failure + */ +int ufshc_send_uic_cmd(struct ufs_host_controller *ufshc, struct ufshc_uic_cmd *uic_cmd) +{ + int ret; + uint32_t arg1; + uint32_t events; + k_timeout_t timeout_event; + + /* Check if the UFS Host Controller is ready to accept a UIC command. */ + if (!ufshc_ready_for_uic_cmd(ufshc)) { + LOG_ERR("Controller is not ready to accept UIC commands"); + ret = -EIO; + goto out; + } + + /* Clear any previous UIC command completion event. */ + (void)k_event_clear(&ufshc->irq_event, + (uint32_t)UFS_UIC_CMD_COMPLETION_EVENT); + + /* Clear the UIC command completion status bit to prepare for the new command */ + ufshc_write_reg(ufshc, UFSHC_IS, UFSHC_IS_UCCS_MASK); + + /* Prepare the UIC command arguments */ + arg1 = (((uint32_t)uic_cmd->mib_attribute << 16U) | + (uint32_t)uic_cmd->gen_sel_index); + ufshc_write_reg(ufshc, UFSHC_UCMDARG1, arg1); + ufshc_write_reg(ufshc, UFSHC_UCMDARG2, + ((uint32_t)uic_cmd->attr_set_type << 16U)); + if (uic_cmd->command == UFSHC_DME_SET_OPCODE) { + ufshc_write_reg(ufshc, UFSHC_UCMDARG3, uic_cmd->mib_value); + } + + /* Write the UIC command opcode to the command register. */ + ufshc_write_reg(ufshc, UFSHC_UICCMD, uic_cmd->command); + + /* Wait for the UIC command completion event */ + timeout_event = K_USEC(UFS_TIMEOUT_US); + events = k_event_wait(&ufshc->irq_event, + (uint32_t)UFS_UIC_CMD_COMPLETION_EVENT, + false, timeout_event); + if ((events & UFS_UIC_CMD_COMPLETION_EVENT) == 0U) { + LOG_ERR("uic cmd 0x%x completion timeout", uic_cmd->command); + ret = -ETIMEDOUT; + goto out; + } + + /* Retrieve the result code from the UFS Host Controller status register. */ + uic_cmd->result_code = (uint8_t)(ufshc_read_reg(ufshc, UFSHC_UCMDARG2) & + UFSHC_UCMDARG2_RESCODE_MASK); + if (uic_cmd->result_code != 0U) { + ret = -EIO; + } else { + if (uic_cmd->command == (uint32_t)UFSHC_DME_GET_OPCODE) { + /* Retrieve the MIB value from the response register */ + uic_cmd->mib_value = ufshc_read_reg(ufshc, UFSHC_UCMDARG3); + } + ret = 0; + } + +out: + return ret; +} + +/** + * @brief Change UFS TX/RX Mphy attributes + * + * @param ufshc: Pointer to the UFS host controller instance. + * @param speed_gear The desired speed gear value for the configuration. + * @param rx_term_cap The RX termination capability to be set. + * @param tx_term_cap The TX termination capability to be set. + * + * @return 0 on success, negative error code on failure + */ +static int32_t ufshc_configure_TxRxAttributes(struct ufs_host_controller *ufshc, + uint32_t speed_gear, uint32_t rx_term_cap, uint32_t tx_term_cap) +{ + struct ufshc_uic_cmd uic_cmd = {0}; + int32_t ret; + uint32_t tx_gear, rx_gear; + uint32_t tx_lanes, rx_lanes; + uint32_t power_mode, rate; + + /* Extracting speed gear components */ + tx_gear = (uint8_t)speed_gear; + rx_gear = (uint8_t)speed_gear; + power_mode = (uint8_t)(speed_gear >> 8U); + rate = (uint8_t)(speed_gear >> 16U); + + /* Configure TX Gear */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_TXGEAR << 16U), + tx_gear, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Configure RX Gear */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_RXGEAR << 16U), + rx_gear, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Configure TX Termination capability */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_TXTERMINATION << 16U), + tx_term_cap, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Configure RX Termination capability */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_RXTERMINATION << 16U), + rx_term_cap, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Check for high-speed configuration (Fast Mode) */ + if (power_mode == UFSHC_TX_RX_FAST) { + /* Configure High-Speed Series based on rate */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_HSSERIES << 16U), + rate, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Program initial ADAPT for Gear 4 */ + if (tx_gear == UFSHC_GEAR4) { + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_TXHSADAPTTYPE << 16U), + 1U, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + } + } + + /* Retrieve and configure the active TX data lanes */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_CONNECTEDTXDATALANES << 16U), + 0U, 0U, + UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + tx_lanes = uic_cmd.mib_value; + + /* Retrieve and configure the active RX data lanes */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_CONNECTEDRXDATALANES << 16U), + 0U, 0U, + UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + rx_lanes = uic_cmd.mib_value; + + /* Configure the active TX data lanes */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_ACTIVETXDATALANES << 16U), + tx_lanes, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Configure the active RX data lanes */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_ACTIVERXDATALANES << 16U), + rx_lanes, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Set the power mode */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)PA_PWRMODE << 16U), + power_mode, 0U, + UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + +out: + return ret; +} + +/** + * @brief Configure the UFS speed gear setting + * + * This function sets the UFS host controller's speed gear by configuring the tx and rx + * attributes (such as termination capabilities) + * + * @param ufshc Pointer to the UFS host controller structure + * @param speed_gear Desired speed gear setting + * + * @return 0 on success, negative error code on failure + */ +int ufshc_configure_speedgear(struct ufs_host_controller *ufshc, uint32_t speed_gear) +{ + int ret = 0; + uint32_t tx_term_cap; + uint32_t rx_term_cap; + uint32_t time_out; + uint32_t event_status; + uint32_t events; + k_timeout_t timeout_event; + + /* Validate the requested speed gear */ + if ((speed_gear != UFSHC_PWM_G1) && (speed_gear != UFSHC_PWM_G2) && + (speed_gear != UFSHC_PWM_G3) && (speed_gear != UFSHC_PWM_G4) && + (speed_gear != UFSHC_HS_G1) && (speed_gear != UFSHC_HS_G2) && + (speed_gear != UFSHC_HS_G3) && (speed_gear != UFSHC_HS_G4) && + (speed_gear != UFSHC_HS_G1_B) && (speed_gear != UFSHC_HS_G2_B) && + (speed_gear != UFSHC_HS_G3_B) && (speed_gear != UFSHC_HS_G4_B)) { + ret = -EINVAL; + goto out; + } + + /* Set termination capabilities based on speed gear type */ + if ((speed_gear == UFSHC_PWM_G1) || (speed_gear == UFSHC_PWM_G2) || + (speed_gear == UFSHC_PWM_G3) || (speed_gear == UFSHC_PWM_G4)) { + /* Pulse Width Modulation (PWM) gears require termination */ + tx_term_cap = 1U; + rx_term_cap = 1U; + } else { + /* High-Speed gears do not require termination */ + tx_term_cap = 0U; + rx_term_cap = 0U; + } + + /* Clear previous UIC power event flag */ + (void)k_event_clear(&ufshc->irq_event, (uint32_t)UFS_UIC_PWR_COMPLETION_EVENT); + + /* Configure the tx and rx attributes for the selected speed gear */ + ret = ufshc_configure_TxRxAttributes(ufshc, speed_gear, + rx_term_cap, tx_term_cap); + if (ret != 0) { + goto out; + } + + /* Wait for UIC power completion event */ + timeout_event = K_USEC(UFS_TIMEOUT_US); + events = k_event_wait(&ufshc->irq_event, + (uint32_t)UFS_UIC_PWR_COMPLETION_EVENT, + false, timeout_event); + if ((events & UFS_UIC_PWR_COMPLETION_EVENT) == 0U) { + ret = -ETIMEDOUT; + goto out; + } + + /* Check power mode status */ + time_out = UFS_TIMEOUT_US; + ret = -EIO; + while (time_out > 0U) { + event_status = ufshc_read_reg(ufshc, UFSHC_HCS) & UFSHC_HCS_UPMCRS_MASK; + if (event_status == UFSHC_PWR_MODE_VAL) { + /* Desired power mode, configuration successful */ + ret = 0; + break; + } + time_out--; + (void)k_usleep(1); + } + +out: + return ret; +} + +/** + * @brief Read the capabilities of the UFS host controller. + * + * This function reads the controller's capabilities register and extracts the + * maximum number of transfer request slots supported by the UFS host controller. + * + * @param ufshc Pointer to the UFS host controller instance. + */ +static inline void ufshc_host_capabilities(struct ufs_host_controller *ufshc) +{ + uint32_t capabilities = ufshc_read_reg(ufshc, UFSHC_HOST_CTRL_CAP); + + ufshc->xfer_req_depth = (capabilities & (uint32_t)UFSHC_TRANSFER_REQ_SLOT_MASK) + 1U; +} + +/** + * @brief Allocate memory for UFS host controller data structures. + * + * This function allocates the required DMA memory for two key data structures: + * 1. The Command Descriptor array (aligned to 128-byte boundaries). + * 2. The UTP Transfer Request Descriptor List (UTRDL) array (aligned to 1k-byte + * boundaries). + * + * If memory has already been allocated for these structures, the function + * avoids redundant allocations. + * + * @param ufshc Pointer to the UFS host controller instance. + * + * @return 0 on success, or a negative error code (e.g., -ENOMEM) on failure + */ +static int32_t ufshc_host_memory_alloc(struct ufs_host_controller *ufshc) +{ + size_t utrd_size, ucd_size; + + /* Allocate 1 memory to 1 Command Descriptor + * Should be aligned to 128 boundary. + */ + ucd_size = (sizeof(struct ufshc_xfer_cmd_desc)); + if (ufshc->ucdl_base_addr == NULL) { + /* don't allocate memory if already allocated - earlier */ + ufshc->ucdl_base_addr = k_aligned_alloc(128, ucd_size); + if (ufshc->ucdl_base_addr == NULL) { + LOG_ERR("Command descriptor memory allocation failed"); + return -ENOMEM; + } + } + (void)memset(ufshc->ucdl_base_addr, 0x0, ucd_size); + + /* Allocate memory to Transfer Request Descriptors based on xfer req depth + * Should be aligned to 1k boundary. + */ + utrd_size = (sizeof(struct ufshc_xfer_req_desc)) * (ufshc->xfer_req_depth); + if (ufshc->utrdl_base_addr == NULL) { + /* don't allocate memory if already allocated - earlier */ + ufshc->utrdl_base_addr = k_aligned_alloc(1024, utrd_size); + if (ufshc->utrdl_base_addr == NULL) { + LOG_ERR("Transfer Descriptor memory allocation failed"); + return -ENOMEM; + } + } + (void)memset(ufshc->utrdl_base_addr, 0x0, utrd_size); + + return 0; +} + +/** + * @brief Handle the completion of SCSI and query commands. + * + * This function processes the completion of SCSI and query commands initiated + * by the UFS host controller. It checks the doorbell register for completed + * requests. Posts an interrupt event indicating the completion of UFS UPIU operations. + * + * @param ufshc The UFS host controller instance. + */ +static void ufshc_transfer_req_compl(struct ufs_host_controller *ufshc) +{ + uint32_t tr_doorbell; + uint32_t completed_reqs; + uint32_t task_tag; + + /* Read the transfer doorbell register to get completed requests status */ + tr_doorbell = ufshc_read_reg(ufshc, UFSHC_UTRLDBR); + + /* Identify which transfer requests have been completed */ + completed_reqs = (~tr_doorbell) & (ufshc->outstanding_xfer_reqs); + + /* Clear the completed requests from the outstanding transfer requests */ + ufshc->outstanding_xfer_reqs &= ~completed_reqs; + + if (completed_reqs != 0U) { + for (task_tag = 0; + task_tag < (uint32_t)(ufshc->xfer_req_depth); + task_tag++) { + (void)sys_test_and_clear_bit((mem_addr_t)((uintptr_t)&completed_reqs), + task_tag); + } + + /* Post an event to indicate that a UPIU completion has occurred */ + (void)k_event_post(&ufshc->irq_event, + (uint32_t)UFS_UPIU_COMPLETION_EVENT); + } +} + +/** + * @brief Handle completion of UIC command interrupts. + * + * This function processes the completion of UIC commands based on the provided + * interrupt status. It handles the UIC command completion and power + * status change events. + * + * @param ufshc The UFS host controller instance. + * @param intr_status The interrupt status generated by the UFS host controller. + */ +static void ufshc_uic_cmd_compl(struct ufs_host_controller *ufshc, uint32_t intr_status) +{ + /* Check if the UIC command status interrupt is set */ + if ((intr_status & UFSHC_IS_UCCS_MASK) != 0U) { + /* Post an event indicating UIC command completion */ + (void)k_event_post(&ufshc->irq_event, + (uint32_t)UFS_UIC_CMD_COMPLETION_EVENT); + } + + /* Check if the power status interrupt is set */ + if ((intr_status & UFSHC_IS_PWR_STS_MASK) != 0U) { + /* Post an event indicating power status change */ + (void)k_event_post(&ufshc->irq_event, + (uint32_t)UFS_UIC_PWR_COMPLETION_EVENT); + } +} + +/** + * @brief Main interrupt service routine for UFS host controller. + * + * This function serves as the main interrupt service routine (ISR) for the UFS host + * controller. UIC and UPIU-related interrupts are handled separately. + * + * @param: Pointer to the UFS host controller instance. + */ +static void ufshc_main_isr(const void *param) +{ + uint32_t intr_status, enabled_intr_status = 0; + struct ufs_host_controller *ufshc = (struct ufs_host_controller *)param; + + /* Read the interrupt status register to get the current interrupt */ + intr_status = ufshc_read_reg(ufshc, UFSHC_IS); + + /* Read the enabled interrupts register and mask it with the current */ + enabled_intr_status = intr_status & ufshc_read_reg(ufshc, UFSHC_IE); + + /* Clear the interrupt status register to acknowledge the interrupts */ + ufshc_write_reg(ufshc, UFSHC_IS, intr_status); + + /* Handle UIC interrupt */ + if ((enabled_intr_status & UFSHCD_UIC_MASK) != 0U) { + ufshc_uic_cmd_compl(ufshc, enabled_intr_status); + } + + /* Handle UPIU interrupt */ + if ((enabled_intr_status & UFSHC_IS_UTRCS_MASK) != 0U) { + ufshc_transfer_req_compl(ufshc); + } +} + +/** + * @brief Enable Host Controller (HCE) + * + * This function enables the Host Controller Enable (HCE) bit, + * and waits for it to be acknowledged by the hardware. + * + * @param ufshc Pointer to the ufs-host-controller instance. + * + * @return 0 on success, or a negative error code on failure. + */ +static int32_t ufshc_set_hce(struct ufs_host_controller *ufshc) +{ + int32_t ret = 0; + uint32_t time_out; + uint32_t read_reg; + + /* Write to the register to enable HCE (Host Controller Enable) */ + ufshc_write_reg(ufshc, UFSHC_HCE, UFSHC_HCE_MASK); + + /* Wait for the HCE bit to be set */ + time_out = UFS_TIMEOUT_US; + do { + read_reg = ufshc_read_reg(ufshc, UFSHC_HCE); + if ((read_reg & UFSHC_HCE_MASK) == UFSHC_HCE_MASK) { + /* Success: HCE bit is set */ + break; + } + time_out = time_out - 1U; + (void)k_usleep(1); + } while (time_out != 0U); + + if (time_out == 0U) { + ret = -ETIMEDOUT; + } + + return ret; +} + +/** + * @brief Enable specific interrupts in the UFS host controller + * + * @param ufshc Pointer to the ufs-host-controller instance. + * @param intrs Interrupt bits to be enabled. + */ +static void ufshc_enable_intr(struct ufs_host_controller *ufshc, uint32_t intrs) +{ + uint32_t set = ufshc_read_reg(ufshc, UFSHC_IE); + + set |= intrs; + ufshc_write_reg(ufshc, UFSHC_IE, set); +} + +/** + * @brief Enable the UFS host controller + * + * This function performs the full sequence to enable the UFS host controller: + * 1. Enables the Host Controller Enable (HCE) bit. + * 2. Waits for the card connection status to indicate that the UFS device is ready. + * 3. Enables UIC-related interrupts. + * + * @param ufshc Pointer to the ufs-host-controller instance. + * + * @return 0 on success, or a negative error code on failure. + */ +static int32_t ufshc_host_enable(struct ufs_host_controller *ufshc) +{ + int32_t ret = 0; + uint32_t time_out; + uint32_t read_reg; + + /* start controller initialization sequence by enabling the HCE */ + ret = ufshc_set_hce(ufshc); + if (ret != 0) { + goto out; + } + + /* Check for Card Connection Status */ + time_out = UFS_TIMEOUT_US; + do { + read_reg = ufshc_read_reg(ufshc, UFSHC_HCS); + if ((read_reg & UFSHC_HCS_CCS_MASK) == 0U) { + /* Success: card is properly connected */ + break; + } + time_out = time_out - 1U; + (void)k_usleep(1); + } while (time_out != 0U); + + if (time_out == 0U) { + ret = -ETIMEDOUT; + } + + /* Enable UIC related interrupts after successful initialization */ + ufshc_enable_intr(ufshc, UFSHCD_UIC_MASK); + +out: + return ret; +} + +/** + * @brief Notify Unipro to perform link startup + * + * This function issues the DME_LINKSTARTUP command to the Unipro layer, + * which triggers the initialization of the Unipro link startup procedure. + * Once the Unipro link is successfully initialized, the device connected to + * the host controller will be detected. + * + * This function attempts to issue the DME_LINKSTARTUP command, and retries + * if necessary. If the link startup process times out, it returns a timeout error. + * + * @param ufshc ufs-host-controller instance + * + * @return 0 on success, non-zero value on failure. + */ +static int32_t ufshc_dme_link_startup(struct ufs_host_controller *ufshc) +{ + struct ufshc_uic_cmd uic_cmd = {0}; + int32_t ret; + uint32_t time_out; + uint32_t time_out_ulss; + uint32_t read_reg; + + /* send DME_LINKSTARTUP command */ + ufshc_fill_uic_cmd(&uic_cmd, 0U, 0U, 0U, UFSHC_DME_LINKSTARTUP_OPCODE); + + time_out = 100U; /* 100 micro seconds */ + do { + time_out = time_out - 1U; + (void)k_usleep(1); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + + /* Clear UIC error which trigger during LINKSTARTUP command */ + ufshc_write_reg(ufshc, UFSHC_IS, UFSHC_IS_UE_MASK); + if (ret == 0) { + /* Success: LINKSTARTUP command was sent successfully */ + break; + } + + /* If the command failed, check if the ULSS (UFS Link Startup) bit is set */ + time_out_ulss = UFS_TIMEOUT_US; + do { + read_reg = ufshc_read_reg(ufshc, UFSHC_IS); + if ((read_reg & UFSHC_IS_ULSS_MASK) == UFSHC_IS_ULSS_MASK) { + /* Success: ULSS bit set, send link startup again */ + break; + } + time_out_ulss = time_out_ulss - 1U; + (void)k_usleep(1); + } while (time_out_ulss != 0U); + + if (time_out_ulss == 0U) { + /* Timeout occurred while waiting for ULSS bit to be set */ + ret = -ETIMEDOUT; + break; + } + + /* prepare for next iteration */ + ufshc_write_reg(ufshc, UFSHC_IS, UFSHC_IS_ULSS_MASK); + } while (time_out != 0U); + + if (ret != 0) { + LOG_ERR("dme-link-startup: error code %d", ret); + } + + if (time_out == 0U) { + ret = -ETIMEDOUT; + } + + return ret; +} + +/** + * @brief Check if any device is connected to the host controller + * + * This function checks if a device is connected to the host controller by + * inspecting the device presence (DP) flag in the UFS Host Controller status register. + * It will time out if no device is detected within the specified timeout period. + * + * @param ufshc ufs-host-controller instance + * + * @return true if a device is present, false if no device is detected or after a timeout. + */ +static inline bool ufshc_is_device_present(struct ufs_host_controller *ufshc) +{ + uint32_t time_out; + uint32_t read_reg; + bool dp_flag = false; + + time_out = UFS_TIMEOUT_US; /* One Second */ + do { + read_reg = ufshc_read_reg(ufshc, UFSHC_HCS); + if ((read_reg & UFSHC_HCS_DP_MASK) != 0U) { + dp_flag = true; + break; + } + time_out = time_out - 1U; + (void)k_usleep(1); + } while (time_out != 0U); + + return dp_flag; +} + +/** + * @brief Initialize the Unipro link startup procedure + * + * This function performs the link startup procedure for the UFS host controller. + * It involves the following steps: + * - Perform PHY initialization. + * - Issue the DME_LINKSTARTUP command to Unipro. + * - Verify if the device is present. + * - Retry the link startup in case of failure. + * + * The function retries the link startup procedure if the device is not detected + * or if an error occurs. The maximum number of retries is controlled by the + * UFSHC_DME_LINKSTARTUP_RETRIES constant. + * + * @param ufshc ufs-host-controller instance + * + * @return 0 on success, non-zero value on failure. + */ +static int32_t ufshc_link_startup(struct ufs_host_controller *ufshc) +{ + int32_t ret; + int32_t retries = UFSHC_DME_LINKSTARTUP_RETRIES; + + /* perform phy initialization */ + ret = ufshc_variant_phy_initialization(ufshc->dev); + if (ret != 0) { + LOG_ERR("Phy setup failed (%d)", ret); + goto out; + } + + do { + /* send DME link start up */ + ret = ufshc_dme_link_startup(ufshc); + + /* check if device is detected by inter-connect layer */ + if (ret == 0) { + if (ufshc_is_device_present(ufshc) == false) { + LOG_ERR("ufs-link-startup: Device is not present"); + ret = -ENXIO; + goto out; + } + } else { + /* + * DME link lost indication is only received when link is up, + * but we can't be sure if the link is up until link startup + * succeeds. So reset the local Uni-Pro and try again. + */ + if (ufshc_host_enable(ufshc) != 0) { + goto out; + } + } + + retries--; + } while ((ret != 0) && (retries != 0)); + + if (ret != 0) { + goto out; + } + + /* Include any host controller configuration via UIC commands */ + ret = ufshc_variant_link_startup_notify(ufshc->dev, (uint8_t)POST_CHANGE); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +/** + * @brief Check the status of UCRDY, UTRLRDY bits. + * + * This function checks if the UCRDY (UFS Ready), UTRLRDY (UFS Transport Layer Ready) + * bits are set in the provided host controller status register. + * + * @param reg Register value of host controller status (HCS) + * + * @return 0 if the status bits are set correctly (ready) else a positive value (1). + */ +static inline int32_t ufshc_get_lists_status(uint32_t reg) +{ + if ((reg & UFSHCD_STATUS_READY) == UFSHCD_STATUS_READY) { + return 0; + } else { + return 1; + } +} + +/** + * @brief Enable the run-stop register to allow host controller operation + * + * This function enables the run-stop register (UTRLRSR) by writing a value that + * allows the host controller to process UFS requests. + * + * @param ufshc Pointer to the UFS host controller instance + */ +static void ufshc_enable_run_stop_reg(struct ufs_host_controller *ufshc) +{ + ufshc_write_reg(ufshc, UFSHC_UTRLRSR, UFSHC_UTRL_RUN); +} + +/** + * @brief Initialize and make UFS controller operational + * + * This function brings the UFS host controller into an operational state by performing + * the following actions: + * 1. Enabling necessary interrupts. + * 2. Programming the UTRL base address for the transport layer. + * 3. Configuring the run-stop register to allow operations. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success; a negative error code on failure. + */ +static int32_t ufshc_make_host_operational(struct ufs_host_controller *ufshc) +{ + int32_t err = 0; + uint32_t reg; + + /* Enable required interrupts */ + ufshc_enable_intr(ufshc, UFSHCD_ENABLE_INTRS); + + /* Configure UTRL base address registers */ + ufshc_write_reg(ufshc, UFSHC_UTRLBA, + (uint32_t)((uintptr_t)ufshc->utrdl_base_addr)); + ufshc_write_reg(ufshc, UFSHC_UTRLBAU, + (uint32_t)(((uint64_t)((uintptr_t)ufshc->utrdl_base_addr)) >> 32U)); + + /* + * UCRDY and UTRLRDY bits must be 1 + */ + reg = ufshc_read_reg(ufshc, UFSHC_HCS); + if ((ufshc_get_lists_status(reg)) == 0) { + ufshc_enable_run_stop_reg(ufshc); + } else { + LOG_ERR("Host controller not ready to process requests"); + err = -EIO; + goto out; + } + +out: + return err; +} + +/** + * @brief Initializes the UFS Host Controller. + * + * This function performs the necessary steps to initialize the UFS host controller. + * + * @param ufshc Pointer to the UFS Host Controller instance. + * + * @return 0 on success, negative value on failure. + */ +static int32_t ufshc_host_initialize(struct ufs_host_controller *ufshc) +{ + int32_t err; + + /* Read capabilities registers */ + ufshc_host_capabilities(ufshc); + + /* Allocate memory for host memory space */ + err = ufshc_host_memory_alloc(ufshc); + if (err != 0) { + LOG_ERR("Memory allocation failed"); + goto out; + } + + /* + * In order to avoid any spurious interrupt immediately after + * registering UFS controller interrupt handler, clear any pending UFS + * interrupt status and disable all the UFS interrupts. + */ + ufshc_write_reg(ufshc, UFSHC_IS, ufshc_read_reg(ufshc, UFSHC_IS)); + ufshc_write_reg(ufshc, UFSHC_IE, 0); + + /* + * Make sure that UFS interrupts are disabled and any pending interrupt + * status is cleared before registering UFS interrupt handler. + */ + if ((uint32_t)irq_connect_dynamic(ufshc->irq, 0, ufshc_main_isr, + (const void *)ufshc, 0) != (ufshc->irq)) { + LOG_ERR("request irq failed"); + err = -ENOTSUP; + goto out; + } else { + irq_enable(ufshc->irq); + } + + /* Set HCE, card connection status, enable uic interrupts */ + err = ufshc_host_enable(ufshc); + if (err != 0) { + LOG_ERR("Host controller enable failed"); + goto out; + } + + /* initialize unipro-phy, link start up */ + err = ufshc_link_startup(ufshc); + if (err != 0) { + goto out; + } + + /* make host operational - enable transfer request */ + err = ufshc_make_host_operational(ufshc); + if (err != 0) { + goto out; + } + +out: + return err; +} + +/** + * @brief Initialize UFS device and check transport layer readiness + * + * This function sends a NOP OUT UPIU to the UFS device and waits for the corresponding + * NOP IN response. It checks if the UTP (Universal Transport Protocol) layer is + * ready after initialization by polling the device's fDeviceInit flag. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success, negative value on failure. + */ +static int32_t ufshc_card_initialize(struct ufs_host_controller *ufshc) +{ + int32_t err; + bool flag_res = true; + uint32_t timeout, slot = 0U; + + /* Send NOP OUT UPIU */ + /* find slot for transfer request */ + ufshc_find_slot_in_trl(ufshc, &slot); + ufshc_fill_nop_upiu(ufshc, slot); + err = ufshc_send_upiu_cmd(ufshc, slot); + if (err != 0) { + LOG_ERR("ufs-card-init: NOP OUT failed %d", err); + goto out; + } + + /* Write fdeviceinit flag */ + err = ufshc_query_flag(ufshc, (int32_t)UFSHC_QRY_SET_FLAG_CMD, + (int32_t)UFSHC_FDEVINIT_FLAG_IDN, 0, NULL); + if (err != 0) { + LOG_ERR("ufs-card-init: setting fDeviceInit flag failed with error %d", err); + goto out; + } + + /* Poll fDeviceInit flag to be cleared */ + timeout = UFS_TIMEOUT_US; /* One Second */ + do { + err = ufshc_query_flag(ufshc, + (int32_t)UFSHC_QRY_READ_FLAG_CMD, + (int32_t)UFSHC_FDEVINIT_FLAG_IDN, 0, + &flag_res); + if (flag_res == false) { + break; + } + timeout = timeout - 1U; + (void)k_usleep(1); + } while (timeout != 0U); + + if ((err == 0) && (flag_res != false)) { + LOG_ERR("ufs-card-init: fDeviceInit was not cleared by the device"); + err = -EBUSY; + } + +out: + return err; +} + +/** + * @brief Reads UFS geometry descriptor + * + * This function reads the UFS Geometry descriptor from the device to determine + * the maximum number of logical units (LUNs) supported by the UFS device. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufshc_read_device_geo_desc(struct ufs_host_controller *ufshc) +{ + int32_t err; + uint8_t desc_buf[UFSHC_QRY_DESC_MAX_SIZE] = {0}; + struct ufs_dev_info *dev_info = &ufshc->dev_info; + + err = ufshc_query_descriptor(ufshc, (int32_t)UFSHC_QRY_READ_DESC_CMD, + (int32_t)UFSHC_GEOMETRY_DESC_IDN, + 0, 0, desc_buf, + (int32_t)UFSHC_QRY_DESC_MAX_SIZE); + if (err != 0) { + LOG_ERR("ufs-read-desc: Failed reading Geometry Desc. err = %d", err); + goto out; + } + + if (desc_buf[UFSHC_GEO_DESC_PARAM_MAX_NUM_LUN] == 1U) { + dev_info->max_lu_supported = 32U; + } else { + dev_info->max_lu_supported = 8U; + } + +out: + return err; +} + +/** + * @brief Initializes UFS device information by reading descriptors + * + * This function reads the UFS Geometry descriptor and updates the device's + * information structure to initialize the UFS device parameters, such as + * the maximum number of supported LUNs. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufshc_read_device_info(struct ufs_host_controller *ufshc) +{ + int32_t ret; + + /* Init UFS geometry descriptor related parameters */ + ret = ufshc_read_device_geo_desc(ufshc); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +/** + * @brief Retrieves information about UFS logical units (LUNs) + * + * This function queries the UFS Unit descriptor for each supported LUN to gather + * detailed information such as LUN ID, block count, block size, and whether the LUN + * is enabled. + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufshc_get_lun_info(struct ufs_host_controller *ufshc) +{ + int32_t ret = -1; + uint8_t desc_buf[UFSHC_QRY_DESC_MAX_SIZE] = {0}; + uint8_t Index, lun_id; + uint8_t lun_enable; + uint32_t TwoPwrblksize; + uint64_t tmpval; + + struct ufs_dev_info *dev_info = &ufshc->dev_info; + + for (Index = 0U; Index < (dev_info->max_lu_supported); Index++) { + + /* Read the Unit Descriptor for Lun Index */ + ret = ufshc_query_descriptor(ufshc, + (int32_t)UFSHC_QRY_READ_DESC_CMD, + (int32_t)UFSHC_UNIT_DESC_IDN, + Index, 0, desc_buf, + (int32_t)UFSHC_QRY_DESC_MAX_SIZE); + if (ret != 0) { + goto out; + } + + /* Check for LU Enable */ + lun_enable = (desc_buf[UFSHC_UD_PARAM_LU_ENABLE]); + + /* if found enabled -- then compute other fields */ + if (lun_enable == 1U) { + dev_info->lun[Index].lun_enabled = true; + + lun_id = (desc_buf[UFSHC_UD_PARAM_UNIT_INDEX]); + dev_info->lun[lun_id].lun_id = lun_id; + + /* compute 2^blocksize */ + TwoPwrblksize = (desc_buf[UFSHC_UD_PARAM_LOGICAL_BLKSZ]); + tmpval = 1; + for (uint32_t Idx = 0; Idx < TwoPwrblksize; Idx++) { + tmpval *= 2U; + } + dev_info->lun[lun_id].block_size = (uint32_t)tmpval; + + /* get block count */ + tmpval = sys_get_be64(&desc_buf[UFSHC_UD_PARAM_LOGICAL_BLKCNT]); + dev_info->lun[lun_id].block_count = tmpval; + } else { + dev_info->lun[Index].lun_enabled = false; + } + } + +out: + return ret; +} + +/** + * @brief Main entry point for executing SCSI requests. + * + * This function is called by the SCSI midlayer to handle SCSI requests. + * It finds a free slot for the transfer request, prepares the command, + * and sends it to the UFS Host Controller. + * + * @param sdev Pointer to the SCSI device. + * @param cmd Pointer to the SCSI command structure. + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufs_scsi_exec(struct scsi_device *sdev, struct scsi_cmd *pccb) +{ + int32_t err; + uint32_t slot = 0U; + struct scsi_host_info *shost = sdev->host; + struct device *ufs_dev = shost->parent; + struct ufs_host_controller *ufshc = ufs_dev->data; + + /* find slot for transfer request */ + ufshc_find_slot_in_trl(ufshc, &slot); + ufshc_fill_scsi_cmd_upiu(ufshc, slot, pccb); + err = ufshc_send_upiu_cmd(ufshc, slot); + + if (err != 0) { + err = -EINVAL; + } + + return err; +} + +static const struct scsi_ops ufs_ops = { + .exec = ufs_scsi_exec, +}; + +/** + * @brief Allocates a UFS SCSI Host Controller instance. + * + * This function allocates a new SCSI host instance for the given UFS + * Host Controller if not already allocated. It initializes the host + * structure and associates it with the UFS controller. + * + * @param ufshc Pointer to the UFS Host Controller structure. + * + * @return 0 on success, -ENOMEM if memory allocation fails. + */ +static int32_t ufshc_alloc_scsi_host(struct ufs_host_controller *ufshc) +{ + struct scsi_host_info *host; + int32_t err = 0; + + if (ufshc->host == NULL) { + /* allocate if not done earlier */ + host = scsi_host_alloc(&ufs_ops); + if (host == NULL) { + LOG_ERR("scsi-add-host: failed"); + err = -ENOMEM; + goto out; + } + ufshc->host = host; + host->parent = ufshc->dev; + host->hostdata = (void *)ufshc; + } + +out: + return err; +} + +/** + * @brief Probes and adds UFS logical units (LUs). + * + * This function probes the UFS device for supported logical units + * and adds them to the SCSI subsystem if they are enabled. + * + * @param ufshc Pointer to the UFS Host Controller structure. + * + * @return 0. + */ +static int32_t ufshc_add_lus(struct ufs_host_controller *ufshc) +{ + struct ufs_dev_info *dev_info = &ufshc->dev_info; + + for (uint32_t i = 0; i < dev_info->max_lu_supported; i++) { + if (dev_info->lun[i].lun_enabled == true) { + (void)scsi_add_lun_host(ufshc->host, &dev_info->lun[i]); + } + } + + return 0; +} + +/** + * @brief Adds a SCSI link for the UFS controller. + * + * This function binds the UFS device to the SCSI subsystem. It allocates + * the SCSI host instance and probes for logical units to be added + * to the host. + * + * @param ufs_dev Pointer to the UFS device structure. + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufs_scsi_bind(struct device *ufs_dev) +{ + int32_t err; + struct ufs_host_controller *ufshc = ufs_dev->data; + + err = ufshc_alloc_scsi_host(ufshc); + if (err != 0) { + goto out; + } + (void)ufshc_add_lus(ufshc); + +out: + return err; +} + +/** + * @brief Initializes the UFS host controller and its associated resources + * + * This function performs the initialization of the UFS host controller + * - Initializing the host and device + * - Reading device-specific information and parameters + * - Retrieving LUN (Logical Unit Number) information + * - Binding the SCSI layer to the UFS device + * + * @param ufshc Pointer to the UFS host controller instance + * + * @return 0 on success, negative error code on failure. + */ +static int32_t ufshc_init(struct ufs_host_controller *ufshc) +{ + int32_t err; + + err = k_mutex_lock(&ufshc->ufs_lock, K_FOREVER); + if (err != 0) { + return err; + } + + /* host initialization */ + err = ufshc_host_initialize(ufshc); + if (err != 0) { + goto out; + } + + /* Check Device Initialization */ + err = ufshc_card_initialize(ufshc); + if (err != 0) { + goto out; + } + + /* device param - UFS descriptors */ + err = ufshc_read_device_info(ufshc); + if (err != 0) { + goto out; + } + + /* read LUN info */ + err = ufshc_get_lun_info(ufshc); + if (err != 0) { + LOG_ERR("Read LUN info failed"); + goto out; + } + + /* register scsi_exec */ + err = ufs_scsi_bind(ufshc->dev); + + /* UFS initialization succeeded */ + if (err == 0) { + ufshc->is_initialized = true; + } + +out: + /* Unlock card mutex */ + (void)k_mutex_unlock(&ufshc->ufs_lock); + + return err; +} + +/** + * @brief Initialize the UFS host controller + * + * This function is responsible for initializing the UFS driver by linking the UFS + * device structure to its corresponding host controller instance and performing + * the necessary initialization steps. + * + * @param ufshc_dev Pointer to the device structure for the UFS host controller + * @param ufshc Pointer to the UFS host controller structure that will be initialized + * + * @return 0 on success, negative error code on failure. + */ +int ufs_init(const struct device *ufshc_dev, struct ufs_host_controller **ufshc) +{ + int err; + + /* Validate input parameters */ + if ((ufshc_dev == NULL) || (ufshc == NULL)) { + return -ENODEV; + } + + /* Link the UFS device to the host controller structure */ + *ufshc = (struct ufs_host_controller *)ufshc_dev->data; + + /* Initialize the UFS host controller */ + err = ufshc_init(*ufshc); + if (err != 0) { + LOG_ERR("Initialization failed with error %d", err); + } + + return err; +} + +/** + * @brief Send raw UPIU commands to the UFS host controller + * + * This function executes raw UPIU commands such as NOP, Query, and SCSI commands + * by using the provided `msgcode`. The caller must fill the UPIU request content + * correctly, as no further validation is performed. The function copies the response + * to the `rsp` parameter if it's not NULL. + * + * @param ufshc Pointer to the UFS host controller structure + * @param msgcode Message code, one of UPIU Transaction Codes Initiator to Target + * @param req Pointer to the request data (raw UPIU command) + * @param rsp Pointer to the response data (raw UPIU reply) + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_exec_raw_upiu_cmd(struct ufs_host_controller *ufshc, + uint32_t msgcode, void *req, void *rsp) +{ + int ret = 0; + struct ufshc_xfer_req_upiu *req_upiu; + struct ufshc_xfer_resp_upiu *rsp_upiu; + struct ufshc_xfer_cmd_desc *ucd_ptr = ufshc->ucdl_base_addr; + struct scsi_cmd *pccb; + uint32_t slot = 0U; + + /* Find an available slot for the transfer request */ + ufshc_find_slot_in_trl(ufshc, &slot); + + switch (msgcode) { + case UFSHC_NOP_UPIU_TRANS_CODE: /* NOP (No Operation) UPIU */ + rsp_upiu = (struct ufshc_xfer_resp_upiu *)rsp; + /* Fill NOP UPIU fields */ + ufshc_fill_nop_upiu(ufshc, slot); + /* Send the NOP UPIU command */ + ret = ufshc_send_upiu_cmd(ufshc, slot); + if (rsp != NULL) { + /* copy the response UPIU */ + (void)memcpy(rsp_upiu, &ucd_ptr->resp_upiu, sizeof(*rsp_upiu)); + } + break; + case UFSHC_QRY_UPIU_TRANS_CODE: /* Query UPIU */ + req_upiu = (struct ufshc_xfer_req_upiu *)req; + rsp_upiu = (struct ufshc_xfer_resp_upiu *)rsp; + /* Fill query UPIU fields */ + ufshc_fill_query_upiu(ufshc, slot, + (int32_t)req_upiu->query_req_upiu.tsf.opcode, + req_upiu->query_req_upiu.tsf.desc_id, + req_upiu->query_req_upiu.tsf.index, + req_upiu->query_req_upiu.tsf.selector, + req_upiu->query_req_upiu.tsf.value, + req_upiu->query_req_upiu.tsf.length); + /* Send the query UPIU command */ + ret = ufshc_send_upiu_cmd(ufshc, slot); + if (rsp != NULL) { + /* Copy the response UPIU */ + (void)memcpy(rsp_upiu, &ucd_ptr->resp_upiu, sizeof(*rsp_upiu)); + } + break; + case UFSHC_CMD_UPIU_TRANS_CODE: /* SCSI command UPIU */ + pccb = (struct scsi_cmd *)req; + /* Fill SCSI command-specific UPIU fields */ + ufshc_fill_scsi_cmd_upiu(ufshc, slot, pccb); + /* Send the SCSI UPIU command */ + ret = ufshc_send_upiu_cmd(ufshc, slot); + if (ret != 0) { + ret = -EINVAL; + } + break; + case UFSHC_TSK_UPIU_TRANS_CODE: /* Task management UPIU command */ + ret = -ENOTSUP; + break; + default: + ret = -EINVAL; + break; + }; + + return ret; +} + +/** + * @brief Read or write descriptors in the UFS host controller + * + * This function reads or writes the specified UFS descriptor based on the operation mode. + * If writing, the contents of `param_buff` are written to the descriptor. If reading, + * the descriptor contents are copied into `param_buff` starting at `param_offset`. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the descriptors + * @param idn Descriptor identifier + * @param index Descriptor index + * @param param_offset Offset for the parameters in the descriptor + * @param param_buff Pointer to the buffer holding the parameters to read/write + * @param param_size Size of the parameter buffer + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_descriptors(struct ufs_host_controller *ufshc, + bool write, uint8_t idn, uint8_t index, + uint8_t param_offset, void *param_buff, + uint8_t param_size) +{ + int ret; + int32_t opcode = (int32_t)UFSHC_QRY_READ_DESC_CMD; + uint8_t desc_buf[UFSHC_QRY_DESC_MAX_SIZE] = {0}; + + /* Validate input parameters */ + if ((param_buff == NULL) || (param_size == 0U)) { + ret = -EINVAL; + goto out; + } + + /* Handle write operation */ + if (write) { + opcode = (int32_t)UFSHC_QRY_WRITE_DESC_CMD; + (void)memcpy((void *)desc_buf, param_buff, param_size); + } + + /* Perform the descriptor query operation (read or write) */ + ret = ufshc_query_descriptor(ufshc, opcode, + (int32_t)idn, index, 0, + desc_buf, (int32_t)UFSHC_QRY_DESC_MAX_SIZE); + if ((ret == 0) && (write == false)) { + /* copy from descriptor buffer to user buffer */ + (void)memcpy(param_buff, + (const void *)(&desc_buf[param_offset]), + param_size); + } + +out: + return ret; +} + +/** + * @brief Send attribute query requests to the UFS host controller + * + * This function performs a read or write operation for a specific UFS attribute + * identified by `idn`. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the attribute + * @param idn Attribute identifier + * @param data Pointer to the attribute value to read/write + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_attributes(struct ufs_host_controller *ufshc, + bool write, uint8_t idn, void *data) +{ + int ret; + int32_t opcode = (int32_t)UFSHC_QRY_READ_ATTR_CMD; + uint32_t attr_value = 0; + + /* Validate input parameters */ + if (data == NULL) { + ret = -EINVAL; + goto out; + } + + /* Handle write operation */ + if (write) { + opcode = (int32_t)UFSHC_QRY_WRITE_ATTR_CMD; + attr_value = *(uint32_t *)data; + } + + /* Perform the attribute query operation (read or write) */ + ret = ufshc_query_attr(ufshc, opcode, (int32_t)idn, 0, 0, &attr_value); + if ((ret == 0) && (write == false)) { + /* copy attribute value */ + (void)memcpy(data, (const void *)&attr_value, sizeof(attr_value)); + } + +out: + return ret; +} + +/** + * @brief Read or write flags in the UFS host controller + * + * This function performs a read or write operation for a UFS flag specified by + * `idn` and `index`. Supports setting, clearing, or reading the flag value. + * + * @param ufshc Pointer to the UFS host controller structure + * @param write Boolean indicating whether to write (true) or read (false) the flags + * @param idn Flag identifier + * @param index Flag index + * @param data Pointer to the data buffer for reading/writing the flag + * + * @return 0 on success, a negative error code on failure. + */ +int ufshc_rw_flags(struct ufs_host_controller *ufshc, + bool write, uint8_t idn, uint8_t index, void *data) +{ + int ret; + int32_t opcode = (int32_t)UFSHC_QRY_READ_FLAG_CMD; + bool flag_value = false; + + /* Validate input parameters */ + if (data == NULL) { + ret = -EINVAL; + goto out; + } + + /* Handle write flag operation */ + if (write) { + flag_value = *(bool *)data; + if (!flag_value) { + /* Clear flag if value is false */ + opcode = (int32_t)UFSHC_QRY_CLR_FLAG_CMD; + } else { + /* Set flag if value is true */ + opcode = (int32_t)UFSHC_QRY_SET_FLAG_CMD; + } + } + + /* Perform the flag query operation (set, clear, or read) */ + ret = ufshc_query_flag(ufshc, opcode, + (int32_t)idn, index, + (bool *)(&flag_value)); + if ((ret == 0) && (write == false)) { + /* copy flag value */ + (void)memcpy(data, (const void *)&flag_value, sizeof(flag_value)); + } + +out: + return ret; +} diff --git a/subsys/ufs/ufs_ops.c b/subsys/ufs/ufs_ops.c new file mode 100644 index 000000000000..ae224691ed42 --- /dev/null +++ b/subsys/ufs/ufs_ops.c @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This file implements requests to configure UFS device. + * It includes functions for handling SCSI Generic (SG) requests + * related to UFS operations querying attributes, flags, and descriptors. + */ + +#include + +/** + * @brief Handle query-based requests for UFS + * + * This function processes query-based IOCTL requests for the UFS device. + * attribute, flag, and descriptor queries. + * + * @param ufshc Pointer to the UFS host controller structure + * @param arg Pointer to the SG IO request structure containing query parameters + * + * @return 0 on success, negative error code on failure + */ +static int32_t ufs_qry_ioctl_request(struct ufs_host_controller *ufshc, + struct sg_io_req *req) +{ + int32_t ret = 0; + int32_t ioctl_id; + struct ufs_sg_req *ufs_req = (struct ufs_sg_req *)req->request; + struct ufs_qry_ioctl_req *qry_ioctl_req = ufs_req->req_qry_ioctl; + bool write_opr; + struct ufs_qry_ioctl_attr *attr_param; + struct ufs_qry_ioctl_flag *flag_param; + struct ufs_qry_ioctl_desc *desc_param; + + if (qry_ioctl_req == NULL) { + return -EINVAL; + } + + ioctl_id = (int32_t)(qry_ioctl_req->ioctl_id); + + /* read or write based on the data transfer direction */ + if (req->dxfer_dir == SG_DXFER_TO_DEV) { + write_opr = true; + } else if (req->dxfer_dir == SG_DXFER_FROM_DEV) { + write_opr = false; + } else { + return -EINVAL; + } + + /* Handle different types of queries */ + switch (ioctl_id) { + case UFS_QRY_IOCTL_ATTR: + attr_param = (struct ufs_qry_ioctl_attr *)&qry_ioctl_req->attr; + ret = ufshc_rw_attributes(ufshc, write_opr, + attr_param->attr_id, + req->dxferp); + break; + case UFS_QRY_IOCTL_FLAG: + flag_param = (struct ufs_qry_ioctl_flag *)&qry_ioctl_req->flag; + ret = ufshc_rw_flags(ufshc, write_opr, + flag_param->flag_id, 0, + req->dxferp); + break; + case UFS_QRY_IOCTL_DESC: + desc_param = (struct ufs_qry_ioctl_desc *)&qry_ioctl_req->desc; + ret = ufshc_rw_descriptors(ufshc, write_opr, + desc_param->desc_id, + desc_param->index, + desc_param->param_offset, + req->dxferp, + (uint8_t)req->dxfer_len); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/** + * @brief Handle a SCSI Generic (SG) request for UFS + * + * This function dispatches an SG_IO request to the appropriate UFS operation. + * + * @param ufshc Pointer to the UFS host controller structure + * @param arg Argument for the request, which could be a UFS query or other data + * + * @return 0 on success, negative error code on failure + */ +int ufs_sg_request(struct ufs_host_controller *ufshc, void *arg) +{ + int ret; + struct sg_io_req *req = (struct sg_io_req *)arg; + struct ufs_sg_req *ufs_req; + int32_t msgcode; + + if ((req == NULL) || (req->protocol != BSG_PROTOCOL_SCSI) || + (req->subprotocol != BSG_SUB_PROTOCOL_SCSI_TRANSPORT) || + (req->request == NULL) || (req->dxferp == NULL)) { + ret = -EINVAL; + goto out; + } + + ufs_req = (struct ufs_sg_req *)req->request; + msgcode = ufs_req->msgcode; + + /* Dispatch based on the message code */ + switch (msgcode) { + case (int32_t)UFS_SG_QUERY_REQ: + ret = ufs_qry_ioctl_request(ufshc, req); + break; + case (int32_t)UFS_SG_TASK_REQ: + ret = -ENOTSUP; + break; + default: + ret = -ENOTSUP; + break; + } + +out: + return ret; +} From a886123297a2049713c1cd50c7cce1c3a6fcb381 Mon Sep 17 00:00:00 2001 From: Ajay Neeli Date: Mon, 17 Mar 2025 12:34:49 +0530 Subject: [PATCH 03/10] dts: bindings: ufshc: Add AMD Versal Gen 2 Add amd,versal2-ufs.yaml for AMD Versal Gen 2 UFS Host Controller. Add ufs-common.yaml for common properties of UFS Host Controllers. Signed-off-by: Ajay Neeli --- dts/bindings/ufs/amd,versal2-ufs.yaml | 47 +++++++++++++++++++++++++++ dts/bindings/ufs/ufs-common.yaml | 26 +++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 dts/bindings/ufs/amd,versal2-ufs.yaml create mode 100644 dts/bindings/ufs/ufs-common.yaml diff --git a/dts/bindings/ufs/amd,versal2-ufs.yaml b/dts/bindings/ufs/amd,versal2-ufs.yaml new file mode 100644 index 000000000000..d6a18ba70704 --- /dev/null +++ b/dts/bindings/ufs/amd,versal2-ufs.yaml @@ -0,0 +1,47 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +title: AMD Versal Gen2 UFS Host Controller. + +description: | + Example usage: + ufs@f10b0000 { + compatible = "amd,versal2-ufs"; + reg = <0xf10b0000 0x1000>; + interrupts = ; + clocks = <&ufs_core_clk>; + clock-names = "core_clk"; + freq-table-hz = <0 0>; + resets = <&scmi_reset 4>, <&scmi_reset 35>; + reset-names = "ufshc-rst", "ufsphy-rst"; + }; + +compatible: "amd,versal2-ufs" + +include: [ufs-common.yaml, reset-device.yaml] + +properties: + clocks: + required: true + description: The list of clocks used by the UFS Host Controller. + + clock-names: + required: true + description: core_clk - Clock for the core logic + + reg: + required: true + description: | + - Host Controller Base address + - IOU(Input-Output Unit) SLCR(System-Level Control Register) + - EFUSE Cache register + - UFS CRP(Clock reset Power) register + + resets: + description: Reset control for both UFS Host Controller and PHY. + + reset-names: + description: | + - Valid names of RESET signals: + - "ufshc-rst" - Reset signal for the UFS Host Controller + - "ufsphy-rst" - Reset signal for the UFS PHY diff --git a/dts/bindings/ufs/ufs-common.yaml b/dts/bindings/ufs/ufs-common.yaml new file mode 100644 index 000000000000..7de381c34244 --- /dev/null +++ b/dts/bindings/ufs/ufs-common.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Common properties for Universal Flash Storage (UFS) Host Controllers + +include: [base.yaml] + +properties: + freq-table-hz: + type: array + description: | + Array of operating frequencies in Hz stored in the same order + as the clocks property. If this property is not defined or a value in the + array is "0" then it is assumed that the frequency is set by the parent + clock or a fixed rate clock source. + + interrupts: + required: true + description: Interrupts used by the UFS Host Controller. + + lanes-per-direction: + type: int + enum: [1, 2] + description: | + Number of lanes available per direction. Note that it is assumed that + same number of lanes are used in both transmit and receive. From 1ce02e0f05f7f08e22b4a723e8a47b7750f29be3 Mon Sep 17 00:00:00 2001 From: Ajay Neeli Date: Mon, 17 Mar 2025 13:06:23 +0530 Subject: [PATCH 04/10] drivers: ufshc: Add AMD Versal Gen 2 UFSHC Add support for the UFS Host Controller (UFSHC) driver on the AMD Versal Gen 2 platform. The implementation includes necessary configurations for M-PHY, RMMI, and Unipro, as well as programming of vendor-specific registers during controller initialization. Signed-off-by: Ajay Neeli --- drivers/CMakeLists.txt | 1 + drivers/Kconfig | 1 + drivers/ufshc/CMakeLists.txt | 9 + drivers/ufshc/Kconfig | 34 ++ drivers/ufshc/Kconfig.amd_versal2 | 10 + drivers/ufshc/ufshc_amd_versal2.c | 787 ++++++++++++++++++++++++++++++ 6 files changed, 842 insertions(+) create mode 100644 drivers/ufshc/CMakeLists.txt create mode 100644 drivers/ufshc/Kconfig create mode 100644 drivers/ufshc/Kconfig.amd_versal2 create mode 100644 drivers/ufshc/ufshc_amd_versal2.c diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 50f64359a391..b1ffe74f6f7d 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -88,6 +88,7 @@ add_subdirectory_ifdef(CONFIG_STEPPER stepper) add_subdirectory_ifdef(CONFIG_SYSCON syscon) add_subdirectory_ifdef(CONFIG_SYS_CLOCK_EXISTS timer) add_subdirectory_ifdef(CONFIG_TEE tee) +add_subdirectory_ifdef(CONFIG_UFSHC ufshc) add_subdirectory_ifdef(CONFIG_VIDEO video) add_subdirectory_ifdef(CONFIG_VIRTUALIZATION virtualization) add_subdirectory_ifdef(CONFIG_W1 w1) diff --git a/drivers/Kconfig b/drivers/Kconfig index 401220c49177..f876d43b839a 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -85,6 +85,7 @@ source "drivers/spi/Kconfig" source "drivers/stepper/Kconfig" source "drivers/syscon/Kconfig" source "drivers/timer/Kconfig" +source "drivers/ufshc/Kconfig" source "drivers/usb/Kconfig" source "drivers/usb_c/Kconfig" source "drivers/video/Kconfig" diff --git a/drivers/ufshc/CMakeLists.txt b/drivers/ufshc/CMakeLists.txt new file mode 100644 index 000000000000..761129d770ba --- /dev/null +++ b/drivers/ufshc/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +if (CONFIG_UFSHC) + +zephyr_library() +zephyr_library_sources_ifdef(CONFIG_UFS_AMD_VERSAL2 ufshc_amd_versal2.c) + +endif() diff --git a/drivers/ufshc/Kconfig b/drivers/ufshc/Kconfig new file mode 100644 index 000000000000..b5c8c509ecec --- /dev/null +++ b/drivers/ufshc/Kconfig @@ -0,0 +1,34 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +# UFS Host Controller (UFSHC) Drivers Configuration +menuconfig UFSHC + bool "UFS Host Controller (UFSHC) drivers" + help + Enable support for UFS host controller drivers. + +if UFSHC + +source "drivers/ufshc/Kconfig.amd_versal2" + +# UFS Host Controller driver initialization priority +config UFSHC_INIT_PRIORITY + int "UFSHC driver init priority" + default 85 + help + Set the initialization priority for the UFSHC driver during system boot. + +# Buffer alignment for UFS Host Controller drivers +config UFSHC_BUFFER_ALIGNMENT + int + default 64 + help + Some UFS host controllers require specific buffer alignment for DMA operations. + Devices should adjust this value if a different alignment is necessary for operation. + This value represents the alignment of data buffers in bytes (default: 64 bytes). + +module = UFSHC +module-str = ufshc +source "subsys/logging/Kconfig.template.log_config" + +endif # UFSHC diff --git a/drivers/ufshc/Kconfig.amd_versal2 b/drivers/ufshc/Kconfig.amd_versal2 new file mode 100644 index 000000000000..6f9be4b7ddef --- /dev/null +++ b/drivers/ufshc/Kconfig.amd_versal2 @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +config UFS_AMD_VERSAL2 + bool "AMD Versal Gen 2 UFS controller driver" + default y + depends on DT_HAS_AMD_VERSAL2_UFS_ENABLED + help + This option enables support for the AMD Versal Gen2 UFS (Universal Flash Storage) + Host Controller. diff --git a/drivers/ufshc/ufshc_amd_versal2.c b/drivers/ufshc/ufshc_amd_versal2.c new file mode 100644 index 000000000000..dee0fe684a17 --- /dev/null +++ b/drivers/ufshc/ufshc_amd_versal2.c @@ -0,0 +1,787 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ufshc_amd_versal2, CONFIG_UFSHC_LOG_LEVEL); + +#define DT_DRV_COMPAT amd_versal2_ufs + +/* UFS Clock Divider Register offset */ +#define VERSAL2_UFS_REG_HCLKDIV_OFFSET 0xFCU + +/* UFS Reset Register offset */ +#define VERSAL2_UFS_CRP_RST_OFFSET 0x340U + +/* UFS Calibration Efuse Register offset */ +#define VERSAL2_UFS_EFUSE_CAL_OFFSET 0xBE8U + +/* SRAM Control and Status Register (CSR) offset */ +#define VERSAL2_UFS_IOU_SLCR_SRAM_CSR_OFFSET 0x104CU + +#define VERSAL2_UFS_SRAM_CSR_BYPASS_MASK 0x4U +#define VERSAL2_UFS_SRAM_CSR_EXTID_DONE_MASK 0x2U +#define VERSAL2_UFS_SRAM_CSR_INIT_DONE_MASK 0x1U + +/* PHY Reset Register offset */ +#define VERSAL2_UFS_IOU_SLCR_PHY_RST_OFFSET 0x1050U + +/* Transmit/Receive Config Ready Register */ +#define VERSAL2_UFS_IOU_SLCR_TX_RX_CFGRDY_OFFSET 0x1054U + +#define VERSAL2_UFS_TX_RX_CFGRDY_MASK GENMASK(3, 0) + +/* RMMI Attributes */ +#define CBREFCLKCTRL2 0x8132 +#define CBCRCTRL 0x811F +#define CBC10DIRECTCONF2 0x810E +#define CBCREGADDRLSB 0x8116 +#define CBCREGADDRMSB 0x8117 +#define CBCREGWRLSB 0x8118 +#define CBCREGWRMSB 0x8119 +#define CBCREGRDLSB 0x811A +#define CBCREGRDMSB 0x811B +#define CBCREGRDWRSEL 0x811C + +#define CBREFREFCLK_GATE_OVR_EN BIT(7) + +/* M-PHY Attributes */ +#define MTX_FSM_STATE 0x41 +#define MRX_FSM_STATE 0xC1 + +#define VERSAL2_UFS_HIBERN8_STATE 0x1U +#define VERSAL2_UFS_SLEEP_STATE 0x2U +#define VERSAL2_UFS_LS_BURST_STATE 0x4U + +/* M-PHY registers */ +#define FAST_FLAGS(n) (0x401C + ((n) * 0x100)) +#define RX_AFE_ATT_IDAC(n) (0x4000 + ((n) * 0x100)) +#define RX_AFE_CTLE_IDAC(n) (0x4001 + ((n) * 0x100)) +#define FW_CALIB_CCFG(n) (0x404D + ((n) * 0x100)) + +#define MPHY_FAST_RX_AFE_CAL BIT(2) +#define MPHY_FW_CALIB_CFG_VAL BIT(8) + +/** + * @brief Configuration for the Versal Gen2 UFS Host Controller. + */ +struct ufshc_versal2_config { + mem_addr_t mmio_base; /**< Base address for the UFS controller memory-mapped I/O */ + uint32_t core_clk_rate; /**< UFS core clock rate in Hz */ + uint32_t irq_id; /**< IRQ line for the UFS controller interrupt */ + mem_addr_t reg_iou_slcr; /**< IOU SLCR register address for UFS configuration */ + mem_addr_t reg_efuse_cache; /**< eFuse cache register address */ + mem_addr_t reg_ufs_crp; /**< UFS CRP register address */ +}; + +/** + * @brief Runtime data for the Versal Gen2 UFS Host Controller. + */ +struct ufshc_versal2_data { + struct ufs_host_controller ufshc; /**< UFS host controller structure */ + uint32_t rx_att_comp_val_l0; /**< Receive AFE compensation value for lane 0 */ + uint32_t rx_att_comp_val_l1; /**< Receive AFE compensation value for lane 1 */ + uint32_t rx_ctle_comp_val_l0; /**< Receive CTLE compensation value for lane 0 */ + uint32_t rx_ctle_comp_val_l1; /**< Receive CTLE compensation value for lane 1 */ +}; + +/** + * @brief Perform variant-specific initialization of the UFS Host Controller. + * + * This function performs a sequence of initialization steps + * including asserting resets, configuring the SRAM and clock dividers, + * and programming the necessary calibration values. + * + * @param dev Pointer to the UFS device handle. + */ +static void ufshc_versal2_initialization(const struct device *dev) +{ + const struct ufshc_versal2_config *cfg = dev->config; + struct ufshc_versal2_data *drvdata = dev->data; + struct ufs_host_controller *ufshc = &drvdata->ufshc; + uint32_t read_reg; + + /* Assert UFS Host Controller Reset */ + sys_write32((1U), ((cfg->reg_ufs_crp) + (mem_addr_t)(VERSAL2_UFS_CRP_RST_OFFSET))); + + /* Assert PHY Reset */ + sys_write32((1U), + ((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_PHY_RST_OFFSET))); + + /* Set ROM Mode (SRAM Bypass) */ + read_reg = sys_read32(((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_SRAM_CSR_OFFSET))); + read_reg |= VERSAL2_UFS_SRAM_CSR_BYPASS_MASK; + read_reg &= ~VERSAL2_UFS_SRAM_CSR_EXTID_DONE_MASK; + sys_write32((read_reg), + ((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_SRAM_CSR_OFFSET))); + + /* Release UFS Host Controller Reset */ + sys_write32((0U), + ((cfg->reg_ufs_crp) + + (mem_addr_t)(VERSAL2_UFS_CRP_RST_OFFSET))); + + /* Program the HCLK_DIV based on the input reference clock */ + ufshc_write_reg(ufshc, + VERSAL2_UFS_REG_HCLKDIV_OFFSET, + (cfg->core_clk_rate / 1000000U)); + + /* Read the calibration values from the eFuse cache */ + read_reg = sys_read32(((cfg->reg_efuse_cache) + + (mem_addr_t)(VERSAL2_UFS_EFUSE_CAL_OFFSET))); + drvdata->rx_att_comp_val_l0 = (uint8_t)read_reg; + drvdata->rx_att_comp_val_l1 = (uint8_t)(read_reg >> 8U); + drvdata->rx_ctle_comp_val_l0 = (uint8_t)(read_reg >> 16U); + drvdata->rx_ctle_comp_val_l1 = (uint8_t)(read_reg >> 24U); +} + +/** + * @brief Notify the UFS controller about link startup. + * + * This function sends a UIC command to change the connection state. + * + * @param dev Pointer to the UFS device handle. + * @param status Notification status (pre-change or post-change). + * + * @return 0 on success, or a negative error code on failure. + */ +static int ufshc_versal2_link_startup_notify(const struct device *dev, uint8_t status) +{ + struct ufshc_versal2_data *drvdata = dev->data; + struct ufs_host_controller *ufshc = &drvdata->ufshc; + struct ufshc_uic_cmd uic_cmd = {0}; + int err = 0; + + if (status == (uint8_t)POST_CHANGE) { + /* change the connection state to the ready state */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)T_CONNECTIONSTATE << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + err = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (err != 0) { + LOG_ERR("Connection setup failed (%d)", err); + } + } + + return err; +} + +/** + * @brief Configures RMMI (Remote Memory-Mapped Interface). + * + * This function configures the UFS host controller's RMMI settings by + * issuing a series of UIC commands to the controller. + * + * @param ufshc Pointer to the UFS host controller driver structure. + * + * @return 0 (success) or non-zero value on failure. + */ +static int32_t ufshc_versal2_set_rmmi(struct ufs_host_controller *ufshc) +{ + struct ufshc_uic_cmd uic_cmd = {0}; + int32_t ret = 0; + + /* Configure the clock reference control to enable ref clock gating */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)CBREFCLKCTRL2 << 16U), + (uint32_t)CBREFREFCLK_GATE_OVR_EN, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Set specific clock configuration */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)CBCRCTRL << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Configure for direct PHY interface mode */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)CBC10DIRECTCONF2 << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Update the M-PHY configuration to apply changes */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)VS_MPHYCFGUPDT << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + +out: + return ret; +} + +/** + * @brief Writes to mPHY registers. + * + * This function writes a 16-bit value to the mPHY register specified by the address. + * + * @param ufshc Pointer to the UFS host controller driver structure. + * @param uic_cmd_ptr Pointer to the UIC command structure to be filled with commands. + * @param address The address of the mPHY register to write. + * @param value The value to write to the specified mPHY register. + * + * @return 0 (success) or non-zero value on failure. + */ +static int32_t ufshc_versal2_write_phy_reg(struct ufs_host_controller *ufshc, + struct ufshc_uic_cmd *uic_cmd_ptr, + uint32_t address, uint32_t value) +{ + int32_t ret = 0; + + /* Write the least significant byte of the register address */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGADDRLSB << 16U), + (uint8_t)address, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Write the most significant byte of the register address */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGADDRMSB << 16U), + (uint8_t)(address >> 8U), 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Write the least significant byte of the register value */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGWRLSB << 16U), + (uint8_t)value, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Write the most significant byte of the register value */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGWRMSB << 16U), + (uint8_t)(value >> 8U), 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Set the read/write select bit in the register */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGRDWRSEL << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Update the M-PHY configuration to apply changes */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)VS_MPHYCFGUPDT << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + +out: + return ret; +} + +/** + * @brief Reads from mPHY registers. + * + * This function reads a 16-bit value from the mPHY register specified by the address. + * + * @param ufshc Pointer to the UFS host controller driver structure. + * @param uic_cmd_ptr Pointer to the UIC command structure to be filled with commands. + * @param address The address of the mPHY register to read. + * @param value Pointer to the variable where the read value will be stored. + * + * @return 0 (success) or non-zero value on failure. + */ +static int32_t ufshc_versal2_read_phy_reg(struct ufs_host_controller *ufshc, + struct ufshc_uic_cmd *uic_cmd_ptr, + uint32_t address, uint32_t *value) +{ + int32_t ret = 0; + + /* Write the least significant byte of the register address */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGADDRLSB << 16U), + (uint8_t)address, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Write the most significant byte of the register address */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGADDRMSB << 16U), + (uint8_t)(address >> 8U), 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Set the read/write select bit in the register */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGRDWRSEL << 16U), + 0U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Update the M-PHY configuration to apply changes */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)VS_MPHYCFGUPDT << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Read the least significant byte of the register value */ + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGRDLSB << 16U), + 0U, 0U, + (uint32_t)UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Read the most significant byte of the register value */ + *value = uic_cmd_ptr->mib_value; + ufshc_fill_uic_cmd(uic_cmd_ptr, + ((uint32_t)CBCREGRDMSB << 16U), + 0U, 0U, + (uint32_t)UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, uic_cmd_ptr); + if (ret != 0) { + goto out; + } + + /* Combine to form the complete register value */ + *value |= (uic_cmd_ptr->mib_value << 8U); + +out: + return ret; +} + +/** + * @brief Configures and sets up UFS Versal Gen2 PHY + * + * This function is used to configure and set up the UFS Versal Gen2 PHY. + * It starts with bypassing the RX-AFE (ATT/CTLE) offset calibrations, + * then programs the ATT and CTLE compensation values. + * Finally, it enables the RX-AFE calibration for Firmware control. + * + * @param dev A pointer to the device structure representing the UFS device. + * + * @return 0 if the setup is successful, or an error code in case of failure. + */ +static int32_t ufs_versal2_setup_phy(const struct device *dev) +{ + struct ufshc_versal2_data *drvdata = dev->data; + struct ufs_host_controller *ufshc = &drvdata->ufshc; + struct ufshc_uic_cmd uic_cmd = {0}; + int32_t ret = 0; + uint32_t read_reg = 0U; + + /* Bypass RX-AFE Calibration for both lanes */ + ret = ufshc_versal2_read_phy_reg(ufshc, &uic_cmd, + FAST_FLAGS(0), + &read_reg); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + FAST_FLAGS(0), + (read_reg | (uint32_t)MPHY_FAST_RX_AFE_CAL)); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_read_phy_reg(ufshc, &uic_cmd, + FAST_FLAGS(1), + &read_reg); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + FAST_FLAGS(1), + (read_reg | (uint32_t)MPHY_FAST_RX_AFE_CAL)); + if (ret != 0) { + return ret; + } + + /* Program ATT Compensation Value for RX */ + if ((drvdata->rx_att_comp_val_l0 != (uint32_t)0x0U) + && (drvdata->rx_att_comp_val_l0 != (uint32_t)0xFFU)) { + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + RX_AFE_ATT_IDAC(0), + drvdata->rx_att_comp_val_l0); + if (ret != 0) { + return ret; + } + } + + if ((drvdata->rx_att_comp_val_l1 != (uint32_t)0x0U) + && (drvdata->rx_att_comp_val_l1 != (uint32_t)0xFFU)) { + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + RX_AFE_ATT_IDAC(1), + drvdata->rx_att_comp_val_l1); + if (ret != 0) { + return ret; + } + } + + /* Program CTLE Compensation Value for RX */ + if ((drvdata->rx_ctle_comp_val_l0 != (uint32_t)0x0U) + && (drvdata->rx_ctle_comp_val_l0 != (uint32_t)0xFFU)) { + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + RX_AFE_CTLE_IDAC(0), + drvdata->rx_ctle_comp_val_l0); + if (ret != 0) { + return ret; + } + } + + if ((drvdata->rx_ctle_comp_val_l1 != (uint32_t)0x0U) + && (drvdata->rx_ctle_comp_val_l1 != (uint32_t)0xFFU)) { + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + RX_AFE_CTLE_IDAC(1), + drvdata->rx_ctle_comp_val_l1); + if (ret != 0) { + return ret; + } + } + + /* Program FW Calibration Config */ + ret = ufshc_versal2_read_phy_reg(ufshc, &uic_cmd, + FW_CALIB_CCFG(0), + &read_reg); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + FW_CALIB_CCFG(0), + (read_reg | (uint32_t)MPHY_FW_CALIB_CFG_VAL)); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_read_phy_reg(ufshc, &uic_cmd, + FW_CALIB_CCFG(1), + &read_reg); + if (ret != 0) { + return ret; + } + + ret = ufshc_versal2_write_phy_reg(ufshc, &uic_cmd, + FW_CALIB_CCFG(1), + (read_reg | (uint32_t)MPHY_FW_CALIB_CFG_VAL)); + + return ret; +} + +/** + * @brief Enable the mphy for the Versal Gen2 UFS host controller. + * + * This function performs the necessary steps to enable the mphy in the Versal Gen2 UFS + * host controller, including sending UIC commands to disable the mphy, update the mphy + * configuration, and wait for the Rx/Tx state machines to de-assert, ensuring that the + * controller is ready for further operations. + * + * @param ufshc Pointer to the UFS host controller structure. + * + * @return 0 (success) or non-zero value on failure. + */ +static int32_t ufshc_versal2_enable_mphy(struct ufs_host_controller *ufshc) +{ + struct ufshc_uic_cmd uic_cmd = {0}; + int32_t ret = 0; + uint32_t time_out; + uint32_t val; + uint32_t index; + + /* Disable the mphy (de-assert mphy_disable) */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)VS_MPHYDISABLE << 16U), + 0U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Update the mphy configuration with new settings */ + ufshc_fill_uic_cmd(&uic_cmd, + ((uint32_t)VS_MPHYCFGUPDT << 16U), + 1U, 0U, + (uint32_t)UFSHC_DME_SET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + + /* Wait for both Tx and Rx state machines to exit busy state */ + for (index = 0U; index < 2U; index++) { + time_out = UFS_TIMEOUT_US; + do { + time_out = time_out - 1U; + (void)k_usleep(1); + /* Read TX_FSM_STATE for the current index */ + ufshc_fill_uic_cmd(&uic_cmd, + (((uint32_t)MTX_FSM_STATE << 16U) | index), + 0U, 0U, + (uint32_t)UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + val = uic_cmd.mib_value; + /* Check if the state machine is in a valid state */ + if ((val == VERSAL2_UFS_HIBERN8_STATE) || + (val == VERSAL2_UFS_SLEEP_STATE) || + (val == VERSAL2_UFS_LS_BURST_STATE)) { + break; + } + } while (time_out != 0U); + + if (time_out == 0U) { + LOG_ERR("Invalid Tx FSM state."); + ret = -ETIMEDOUT; + goto out; + } + + time_out = UFS_TIMEOUT_US; + do { + time_out = time_out - 1U; + (void)k_usleep(1); + /* Read RX_FSM_STATE for the current index */ + ufshc_fill_uic_cmd(&uic_cmd, + (((uint32_t)MRX_FSM_STATE << 16U) | (4U + index)), + 0U, 0U, + (uint32_t)UFSHC_DME_GET_OPCODE); + ret = ufshc_send_uic_cmd(ufshc, &uic_cmd); + if (ret != 0) { + goto out; + } + val = uic_cmd.mib_value; + /* Check if the state machine is in a valid state */ + if ((val == VERSAL2_UFS_HIBERN8_STATE) || + (val == VERSAL2_UFS_SLEEP_STATE) || + (val == VERSAL2_UFS_LS_BURST_STATE)) { + break; + } + } while (time_out != 0U); + + if (time_out == 0U) { + LOG_ERR("Invalid Rx FSM state."); + ret = -ETIMEDOUT; + goto out; + } + } + +out: + return ret; +} + +/** + * @brief Initializes the PHY for the Versal Gen2 UFS Host Controller + * + * This function performs the initialization of the PHY for the Versal Gen2 UFS Host Controller. + * It involves several steps including waiting for various status registers to de-assert, + * de-asserting the PHY reset, waiting for SRAM initialization, performing calibration + * bypasses, and programming various compensation values. Additionally, firmware calibration + * settings are configured, and the mPHY is enabled. + * + * @param dev Pointer to the device structure representing the UFS device. + * + * @return 0 (success) or non-zero value on failure. + */ +static int ufshc_versal2_phy_init(const struct device *dev) +{ + const struct ufshc_versal2_config *cfg = dev->config; + struct ufshc_versal2_data *drvdata = dev->data; + struct ufs_host_controller *ufshc = &drvdata->ufshc; + int ret = 0; + uint32_t read_reg; + uint32_t time_out; + + /* + * Wait for the Tx/Rx CfgRdyn signal to de-assert, indicating that + * configuration of the UFS Tx/Rx lanes is complete and stable. This + * ensures that the UFS PHY is ready for further initialization. + */ + time_out = UFS_TIMEOUT_US; + do { + read_reg = sys_read32(((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_TX_RX_CFGRDY_OFFSET))); + if ((read_reg & (uint32_t)VERSAL2_UFS_TX_RX_CFGRDY_MASK) == 0U) { + break; + } + time_out = time_out - 1U; + (void)k_usleep(1); + } while (time_out != 0U); + + if (time_out == 0U) { + LOG_ERR("Tx/Rx configuration signal busy."); + ret = -ETIMEDOUT; + goto out; + } + + ret = ufshc_versal2_set_rmmi(ufshc); + if (ret != 0) { + goto out; + } + + /* De-assert PHY reset */ + sys_write32((0U), + ((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_PHY_RST_OFFSET))); + + /* Wait for SRAM initialization to complete */ + time_out = UFS_TIMEOUT_US; + do { + read_reg = sys_read32(((cfg->reg_iou_slcr) + + (mem_addr_t)(VERSAL2_UFS_IOU_SLCR_SRAM_CSR_OFFSET))); + if ((read_reg & VERSAL2_UFS_SRAM_CSR_INIT_DONE_MASK) != 0U) { + break; + } + time_out = time_out - 1U; + (void)k_usleep(1); + } while (time_out != 0U); + + if (time_out == 0U) { + LOG_ERR("SRAM initialization failed."); + ret = -ETIMEDOUT; + goto out; + } + + /* Setup calibration settings */ + ret = ufs_versal2_setup_phy(dev); + if (ret != 0) { + goto out; + } + + /* Enable mPHY */ + ret = ufshc_versal2_enable_mphy(ufshc); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +/** + * @brief Performs device instance init for Versal Gen2 UFS controller + * + * This function initializes the UFS controller instance by setting up + * the various fields in the host controller structure, configuring the + * mutexes and events, and performing the required PHY reset and reference + * clock programming for the UFS device. + * + * @param dev Pointer to the device structure for the UFS controller instance + * + * @return 0 on success. + */ +static int32_t ufshc_versal2_init(const struct device *dev) +{ + const struct ufshc_versal2_config *cfg = dev->config; + struct ufshc_versal2_data *drvdata = dev->data; + struct ufs_host_controller *ufshc = &drvdata->ufshc; + + /* Initialize UFS driver structure and device data */ + (void)memset(ufshc, 0x0, sizeof(*ufshc)); + ufshc->dev = (struct device *)((uintptr_t)dev); + ufshc->mmio_base = cfg->mmio_base; + ufshc->is_initialized = false; + ufshc->irq = cfg->irq_id; + drvdata->rx_att_comp_val_l0 = 0U; + drvdata->rx_att_comp_val_l1 = 0U; + drvdata->rx_ctle_comp_val_l0 = 0U; + drvdata->rx_ctle_comp_val_l1 = 0U; + ufshc->is_cache_coherent = 0U; + + /* Initialize and lock UFS card mutex and events */ + k_event_init(&ufshc->irq_event); + (void)k_mutex_init(&ufshc->ufs_lock); + + /* Perform PHY reset and program reference clock */ + ufshc_versal2_initialization(dev); + + return 0; +} + +static DEVICE_API(ufshc, ufshc_versal2_api) = { + .phy_initialization = ufshc_versal2_phy_init, + .link_startup_notify = ufshc_versal2_link_startup_notify, +}; + +/** + * UFSHC_VERSAL2_INIT - Macro to initialize a UFS host controller instance for Versal Gen2 + * @n: The instance index of the UFS controller + * + * This macro initializes the configuration, data structures, and device + * instance for a UFS host controller instance. Creates a device entry for the + * UFS host controller in Zephyr device model. + */ +#define UFSHC_VERSAL2_INIT(n) \ + \ + static const struct ufshc_versal2_config ufshc_versal2_config_##n = { \ + .mmio_base = DT_INST_REG_ADDR_BY_IDX(n, 0), \ + .core_clk_rate = DT_PROP(DT_INST_PHANDLE_BY_NAME(n, clocks, core_clk), \ + clock_frequency), \ + .irq_id = DT_INST_IRQN(n), \ + .reg_iou_slcr = DT_INST_REG_ADDR_BY_IDX(n, 1), \ + .reg_efuse_cache = DT_INST_REG_ADDR_BY_IDX(n, 2), \ + .reg_ufs_crp = DT_INST_REG_ADDR_BY_IDX(n, 3), \ + }; \ + \ + static struct ufshc_versal2_data ufshc_versal2_data_##n = { \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, &ufshc_versal2_init, NULL, \ + &ufshc_versal2_data_##n, &ufshc_versal2_config_##n, \ + POST_KERNEL, CONFIG_UFSHC_INIT_PRIORITY, \ + &ufshc_versal2_api); + +DT_INST_FOREACH_STATUS_OKAY(UFSHC_VERSAL2_INIT) From 8f9a874f4e04c5728da4163f033592b8ff898736 Mon Sep 17 00:00:00 2001 From: Ajay Neeli Date: Mon, 17 Mar 2025 13:11:31 +0530 Subject: [PATCH 05/10] dts: bindings: Add dts for Zephyr UFS disk Add device tree binding for UFS disks in Zephyr, enabling interaction with UFS storage via the Zephyr disk subsystem. This binding ensures correct handling of UFS devices, including support for multiple LUNs, enabling filesystem operations in Zephyr. Key properties: - lun: Defines the Logical Unit Number (LUN) for the UFS disk, defaulting to 0. This is essential for systems with multiple LUNs. - disk-name: A required string specifying the UFS disk's name for proper registration in the disk subsystem. Signed-off-by: Ajay Neeli --- dts/bindings/ufs/zephyr,ufs-disk.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 dts/bindings/ufs/zephyr,ufs-disk.yaml diff --git a/dts/bindings/ufs/zephyr,ufs-disk.yaml b/dts/bindings/ufs/zephyr,ufs-disk.yaml new file mode 100644 index 000000000000..06c85d13a399 --- /dev/null +++ b/dts/bindings/ufs/zephyr,ufs-disk.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: | + Zephyr UFS disk. + + A binding with this compatible present within a UFS host controller device node indicates + that a UFS disk device is attached to a specific UFS LUN. It enables the disk to be used + with the disk driver API and any relevant subsystems, such as Filesystems. + +compatible: "zephyr,ufs-disk" + +include: base.yaml + +properties: + lun: + type: int + default: 0 + description: | + Specifies the UFS LUN for disk operations. The default is LUN 0 if not + explicitly defined. The range for LUN values is 0 to 31. + + disk-name: + type: string + required: true + description: The name of the disk, used by the Disk subsystem for registering the UFS disk. From c7a2170bd5964dddc93dbc287722308a48e8c538 Mon Sep 17 00:00:00 2001 From: Ajay Neeli Date: Mon, 17 Mar 2025 14:19:28 +0530 Subject: [PATCH 06/10] drivers: disk: Add UFS disk driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add UFS disk driver, enabling interaction with UFS storage devices via the filesystem interface. It recognizes Logical Unit Numbers (LUNs) as separate disks and presents UFS devices as block devices for standard filesystem operations. This driver ensures UFS devices are recognized and usable within Zephyr’s filesystem. Key features: - Initialization: Initializes the UFS disk using LUN and disk-name properties from the device tree. - Disk Operations: Registers essential operations, including read, write, and ioctl. Signed-off-by: Ajay Neeli --- drivers/disk/CMakeLists.txt | 1 + drivers/disk/Kconfig | 1 + drivers/disk/Kconfig.ufs | 30 +++ drivers/disk/ufsdisk.c | 516 ++++++++++++++++++++++++++++++++++++ 4 files changed, 548 insertions(+) create mode 100644 drivers/disk/Kconfig.ufs create mode 100644 drivers/disk/ufsdisk.c diff --git a/drivers/disk/CMakeLists.txt b/drivers/disk/CMakeLists.txt index 0202238a1d2a..5bde04b4d5f3 100644 --- a/drivers/disk/CMakeLists.txt +++ b/drivers/disk/CMakeLists.txt @@ -8,6 +8,7 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_FLASH flashdisk.c) zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_RAM ramdisk.c) zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_LOOPBACK loopback_disk.c) +zephyr_library_sources_ifdef(CONFIG_DISK_DRIVER_UFS ufsdisk.c) zephyr_library_sources_ifdef(CONFIG_SDMMC_STM32 sdmmc_stm32.c) zephyr_library_sources_ifdef(CONFIG_SDMMC_SUBSYS sdmmc_subsys.c) diff --git a/drivers/disk/Kconfig b/drivers/disk/Kconfig index 9c1723e6e8d3..3a608afbb468 100644 --- a/drivers/disk/Kconfig +++ b/drivers/disk/Kconfig @@ -13,6 +13,7 @@ source "drivers/disk/Kconfig.flash" source "drivers/disk/Kconfig.sdmmc" source "drivers/disk/Kconfig.mmc" source "drivers/disk/Kconfig.loopback" +source "drivers/disk/Kconfig.ufs" rsource "nvme/Kconfig" diff --git a/drivers/disk/Kconfig.ufs b/drivers/disk/Kconfig.ufs new file mode 100644 index 000000000000..e0831daac35d --- /dev/null +++ b/drivers/disk/Kconfig.ufs @@ -0,0 +1,30 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +config DISK_DRIVER_UFS + bool "Disk operations on UFS card" + default y if DT_HAS_ZEPHYR_UFS_DISK_ENABLED + select SCSI + select UFS_STACK + help + UFS device as disk driver. + +if DISK_DRIVER_UFS + +config UFSDISK_INIT_PRIORITY + int "Init priority" + default 90 + help + UFS disk device driver initialization priority + +config UFS_BUFFER_SIZE + int + default 4096 + help + Size in bytes of internal buffer UFS card uses for unaligned reads. + +module = UFSDISK +module-str = ufsdisk +source "subsys/logging/Kconfig.template.log_config" + +endif # DISK_DRIVER_UFS diff --git a/drivers/disk/ufsdisk.c b/drivers/disk/ufsdisk.c new file mode 100644 index 000000000000..4fa488a16eea --- /dev/null +++ b/drivers/disk/ufsdisk.c @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * UFS disk driver using zephyr UFS-SCSI subsystem + * - Uses scsi read/write APIs for disk read/write operations. + * - Uses IOCTL to issue UFS Query requests. + * - LUN number from device tree should be enabled in UFS device. + * - LUN-0 is used, when LUN is not specified in device tree. + * - For unaligned memory read/writes, a local buffer is used. + */ + +#define DT_DRV_COMPAT zephyr_ufs_disk + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ufsdisk, CONFIG_UFSDISK_LOG_LEVEL); + +/** + * @brief UFS disk status. + */ +enum ufsdisk_status { + UFS_UNINIT, /**< Un-Initialized UFS Disk. */ + UFS_ERROR, /**< UFS Disk Initialization Failed. */ + UFS_OK, /**< UFS Disk Operational. */ +}; + +/** + * @brief Runtime data structure for a UFS disk. + */ +struct ufsdisk_data { + struct disk_info info; /**< Disk information structure. */ + const struct device *ufshc_device; /**< UFS Host Controller device. */ + uint8_t lun; /**< Logical Unit Number (LUN) for the disk. */ + struct ufs_host_controller *ufshc; /**< UFS Host Controller instance. */ + struct scsi_device *sdev; /**< SCSI device associated with the UFS disk. */ + uint8_t *card_buffer; /**< Temporary buffer for unaligned I/O operations. */ + enum ufsdisk_status status; /**< Current status of the UFS disk. */ +}; + +/** + * @brief Initializes the UFS disk access. + * + * This function registers the host controller and verify the device is ready. + * It also allocates a buffer for unaligned reads/writes. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * + * @return 0 on success, or a negative error code on failure. + */ +static int32_t disk_ufs_access_init(struct disk_info *disk) +{ + struct ufsdisk_data *data = (struct ufsdisk_data *)disk; + int32_t err; + + if (data->status == UFS_OK) { + /* called twice, don't reinit */ + err = 0; + goto out; + } + + /* set status as error */ + data->status = UFS_ERROR; + + /* Register host controller with UFS card */ + if ((data->ufshc != NULL) && (data->ufshc->is_initialized)) { + err = 0; /* already initialized */ + } else { + err = ufs_init(data->ufshc_device, &data->ufshc); + if (err != 0) { + LOG_ERR("UFS initialization failed %d", err); + goto out; + } + } + + /* Look up the SCSI device for the specified LUN */ + data->sdev = scsi_device_lookup_by_host(data->ufshc->host, data->lun); + if (data->sdev == NULL) { + LOG_ERR("SCSI device for lun:%d is NULL", data->lun); + err = -ENOTSUP; + goto out; + } + + /* Verify device readiness */ + err = scsi_ioctl(data->sdev, SCSI_IOCTL_TEST_UNIT_READY, NULL); + if (err != 0) { + LOG_ERR("Failed to execute TUR, lun:%d", data->lun); + goto out; + } + + /* Allocate a temporary buffer for unaligned reads/writes */ + data->card_buffer = (uint8_t *)k_aligned_alloc(CONFIG_UFSHC_BUFFER_ALIGNMENT, + CONFIG_UFS_BUFFER_SIZE); + if (data->card_buffer == NULL) { + err = -ENOMEM; + goto out; + } + + /* Initialization complete */ + data->status = UFS_OK; + +out: + return err; +} + +/** + * @brief Returns the current status of the UFS disk. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * + * @return Disk status + */ +static int32_t disk_ufs_access_status(struct disk_info *disk) +{ + struct ufsdisk_data *data = (struct ufsdisk_data *)disk; + int32_t err; + + if (data->status == UFS_OK) { + err = DISK_STATUS_OK; + } else { + err = DISK_STATUS_UNINIT; + } + + return err; +} + +/** + * @brief Reads data from UFS card using SCSI read API. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * @param rbuf Pointer to the buffer where the read data will be stored. + * @param start_block The starting block number to read from. + * @param num_blocks The number of blocks to read. + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t ufs_card_read_blocks(struct disk_info *disk, + uint8_t *rbuf, + uint32_t start_block, + uint32_t num_blocks) +{ + int32_t ret; + uint32_t rlen; + uint32_t sector = 0; + uint32_t buf_offset = 0; + + /* Get the UFS device and host controller from the SCSI device */ + struct ufsdisk_data *disk_data = (struct ufsdisk_data *)disk; + struct scsi_device *scsi_dev = disk_data->sdev; + const struct device *ufs_dev = scsi_dev->host->parent; + struct ufs_host_controller *ufshc = (struct ufs_host_controller *)ufs_dev->data; + + /* Validate if requested blocks are within the disk's capacity */ + if ((start_block + num_blocks) > (scsi_dev->capacity)) { + return -EINVAL; + } + + /* Lock UFS host controller for access */ + ret = k_mutex_lock(&ufshc->ufs_lock, K_FOREVER); + if (ret != 0) { + LOG_ERR("could not get UFS card mutex"); + return -EBUSY; + } + + /* Handle aligned and unaligned buffer reads */ + if ((((uintptr_t)rbuf) & ((uintptr_t)CONFIG_UFSHC_BUFFER_ALIGNMENT - (uintptr_t)1)) + != (uintptr_t)0) { + /* Unaligned buffer, use temporary internal buffer */ + rlen = (uint32_t)((uint32_t)CONFIG_UFS_BUFFER_SIZE / (scsi_dev->sector_size)); + if (rlen == 0U) { + LOG_ERR("Card buffer size is less than block size - unaligned"); + ret = -ENOBUFS; + goto out; + } + rlen = MIN(num_blocks, rlen); + if (disk_data->card_buffer == NULL) { + ret = -ENOMEM; + goto out; + } + (void)memset(disk_data->card_buffer, 0x0, + (size_t)(uint32_t)(rlen * (scsi_dev->sector_size))); + while (sector < num_blocks) { + /* Read from UFS device to internal buffer */ + ret = scsi_read(scsi_dev, + (uint64_t)(uint32_t)(sector + start_block), + rlen, + disk_data->card_buffer); + if (ret != 0) { + LOG_ERR("UFS Card read failed"); + goto out; + } + /* Copy data from internal buffer to user buffer */ + (void)memcpy(&rbuf[buf_offset], + disk_data->card_buffer, + (size_t)(uint32_t)(rlen * (scsi_dev->sector_size))); + sector += rlen; + buf_offset += rlen * (scsi_dev->sector_size); + } + + } else { + /* Aligned buffer, use directly */ + ret = scsi_read(scsi_dev, start_block, num_blocks, rbuf); + if (ret != 0) { + LOG_ERR("UFS Card read failed"); + goto out; + } + } + +out: + (void)k_mutex_unlock(&ufshc->ufs_lock); + + return ret; +} + +/** + * @brief Reads data from the UFS disk. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * @param buf Pointer to the buffer where the read data will be stored. + * @param sector The starting sector number to read from. + * @param count The number of sectors to read. + * + * @return 0 on success, a negative error code on failure. + */ +static int32_t disk_ufs_access_read(struct disk_info *disk, + uint8_t *buf, + uint32_t sector, + uint32_t count) +{ + uint32_t last_sector = sector + count; + + if (last_sector < sector) { + return -EIO; /* Overflow check */ + } + + return ufs_card_read_blocks(disk, buf, sector, count); +} + +/** + * @brief Writes data to UFS card using SCSI write API. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * @param wbuf Pointer to the buffer containing the data to be written. + * @param start_block The starting block number to write to. + * @param num_blocks The number of blocks to write. + * + * @return zero if successful or an error if Write failed. + */ +static int32_t ufs_card_write_blocks(struct disk_info *disk, + const uint8_t *wbuf, + uint32_t start_block, + uint32_t num_blocks) +{ + int32_t ret; + uint32_t wlen; + uint32_t sector; + const uint8_t *buf_offset; + + /* Get the UFS device and host controller from the SCSI device */ + struct ufsdisk_data *disk_data = (struct ufsdisk_data *)disk; + struct scsi_device *scsi_dev = disk_data->sdev; + const struct device *ufs_dev = scsi_dev->host->parent; + struct ufs_host_controller *ufshc = (struct ufs_host_controller *)ufs_dev->data; + + /* Validate if requested blocks are within the disk's capacity */ + if ((start_block + num_blocks) > (scsi_dev->capacity)) { + return -EINVAL; + } + + /* Lock UFS host controller for access */ + ret = k_mutex_lock(&ufshc->ufs_lock, K_FOREVER); + if (ret != 0) { + LOG_ERR("could not get UFS card mutex"); + return -EBUSY; + } + + /* Handle aligned and unaligned buffer writes */ + if ((((uintptr_t)wbuf) & ((uintptr_t)CONFIG_UFSHC_BUFFER_ALIGNMENT - (uintptr_t)1)) + != (uintptr_t)0) { + /* Unaligned buffer, use temporary internal buffer */ + wlen = (uint32_t)((uint32_t)CONFIG_UFS_BUFFER_SIZE / (scsi_dev->sector_size)); + if (wlen == 0U) { + LOG_ERR("Card buffer size is less than block size - unaligned"); + ret = -ENOBUFS; + goto out; + } + wlen = MIN(num_blocks, wlen); + sector = 0; + buf_offset = wbuf; + if (disk_data->card_buffer == NULL) { + ret = -ENOMEM; + goto out; + } + (void)memset(disk_data->card_buffer, + 0x0, + (size_t)(uint32_t)(wlen * (scsi_dev->sector_size))); + while (sector < num_blocks) { + /* Copy data from user buffer to internal buffer */ + (void)memcpy(disk_data->card_buffer, buf_offset, + (size_t)(uint32_t)(wlen * (scsi_dev->sector_size))); + /* Write from internal buffer to UFS device */ + ret = scsi_write(scsi_dev, + (uint64_t)(uint32_t)(sector + start_block), + wlen, + disk_data->card_buffer); + if (ret != 0) { + LOG_ERR("UFS Card write failed"); + goto out; + } + /* Increase sector count and buffer offset */ + sector += wlen; + buf_offset += wlen * (scsi_dev->sector_size); + } + } else { + /* Aligned buffer, use directly */ + ret = scsi_write(scsi_dev, start_block, num_blocks, wbuf); + if (ret != 0) { + LOG_ERR("UFS Card write failed"); + goto out; + } + } + +out: + (void)k_mutex_unlock(&ufshc->ufs_lock); + + return ret; +} + +/** + * @brief Write data to UFS disk. + * + * @param disk Pointer to the disk information structure. + * @param buf Pointer to the buffer containing data to be written. + * @param sector The starting sector to write to. + * @param count The number of sectors to write. + * + * @return 0 on success, negative error code on failure. + */ +static int32_t disk_ufs_access_write(struct disk_info *disk, + const uint8_t *buf, + uint32_t sector, + uint32_t count) +{ + uint32_t last_sector = sector + count; + + if (last_sector < sector) { + return -EINVAL; /* Overflow check */ + } + + return ufs_card_write_blocks(disk, buf, sector, count); +} + +/** + * @brief Handle IOCTL commands for UFS disk. + * + * This function handles various IOCTL commands such as initialization, + * de-initialization, sector size retrieval, or executing SCSI or UFS requests. + * + * The behavior of the function depends on the value of the @cmd parameter: + * + * - DISK_IOCTL_CTRL_INIT: Initialize the UFS disk. + * - DISK_IOCTL_CTRL_DEINIT: Deinitialize the UFS disk. + * - DISK_IOCTL_CTRL_SYNC: No operation (typically used for synchronization). + * - DISK_IOCTL_GET_SECTOR_COUNT: Return the total number of sectors in the disk. + * - DISK_IOCTL_GET_SECTOR_SIZE: Return the sector size of the disk. + * - DISK_IOCTL_GET_ERASE_BLOCK_SZ: Return the erase block size of the disk (in sectors). + * - SG_IO: Handle SCSI or UFS-specific IO requests via the sg_io_req structure + * pointed to by buff. This includes processing SCSI commands or UFS-specific + * commands using the appropriate protocol and subprotocol. + * + * @param disk Pointer to the disk_info structure representing the UFS disk. + * @param cmd The IOCTL command to process. + * @param buff Pointer to the buffer for input/output data (depends on command). + * + * @return 0 on success, negative error code on failure. + */ +static int32_t disk_ufs_access_ioctl(struct disk_info *disk, uint8_t cmd, void *buff) +{ + struct ufsdisk_data *data = (struct ufsdisk_data *)disk; + struct scsi_device *sdev = data->sdev; + struct ufs_host_controller *ufshc = data->ufshc; + struct sg_io_req *req; + int32_t ret = 0; + + switch (cmd) { + case DISK_IOCTL_CTRL_INIT: + /* Initialize the disk */ + ret = disk_ufs_access_init(disk); + break; + case DISK_IOCTL_CTRL_DEINIT: + /* De-initialize the disk */ + /* mark the disk as uninitialized and free memory */ + data->status = UFS_UNINIT; + k_free(data->card_buffer); + data->card_buffer = NULL; + break; + case DISK_IOCTL_CTRL_SYNC: + break; + case DISK_IOCTL_GET_SECTOR_COUNT: + /* Retrieve the total number of sectors on the disk */ + if (buff != NULL) { + *(uint32_t *)buff = (uint32_t)(sdev->capacity); + } else { + ret = -EINVAL; + } + break; + case DISK_IOCTL_GET_SECTOR_SIZE: + /* Retrieve the size of each sector on the disk */ + if (buff != NULL) { + *(uint32_t *)buff = (sdev->sector_size); + } else { + ret = -EINVAL; + } + break; + case DISK_IOCTL_GET_ERASE_BLOCK_SZ: + /* Retrieve the block size for erase */ + if (buff != NULL) { + *(uint32_t *)buff = 1U; + } else { + ret = -EINVAL; + } + break; + case SG_IO: + /* Handle SCSI or UFS-specific I/O requests */ + req = (struct sg_io_req *)buff; + + if ((req == NULL) || + (req->protocol != BSG_PROTOCOL_SCSI)) { + ret = -EINVAL; + } else { + /*Process SCSI or UFS depending on the subprotocol */ + if (req->subprotocol == BSG_SUB_PROTOCOL_SCSI_CMD) { + /* Handle SCSI-specific I/O operation */ + ret = scsi_ioctl(sdev, (int32_t)cmd, req); + } else { + /* Handle UFS-specific I/O operation */ + ret = ufs_sg_request(ufshc, req); + } + } + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/** + * @brief Disk operations for UFS disk. + * + * This structure contains the disk operations for UFS disk management, + * including initialization, status checking, read, write, and ioctl handling. + */ +static const struct disk_operations ufs_disk_ops = { + .init = disk_ufs_access_init, + .status = disk_ufs_access_status, + .read = disk_ufs_access_read, + .write = disk_ufs_access_write, + .ioctl = disk_ufs_access_ioctl, +}; + +#define DEFINE_UFSDISKS_DEVICE(n) \ + { \ + .info = { \ + .ops = &ufs_disk_ops, \ + .name = DT_INST_PROP(n, disk_name), \ + }, \ + .ufshc_device = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .lun = DT_INST_PROP(n, lun), \ + }, + +/** + * @brief Array of UFS disk data structures. + * + * This array is populated using the device tree macros, which define the + * UFS disks and their properties such as the disk name and LUN. + */ +static struct ufsdisk_data ufs_disks[] = { + DT_INST_FOREACH_STATUS_OKAY(DEFINE_UFSDISKS_DEVICE) +}; + +/** + * @brief Register UFS disks. + * + * This function registers the UFS disk devices during the + * system initialization phase. + * + * @return 0 on success, negative error code on failure. + */ +static int32_t disk_ufs_register(void) +{ + int32_t err = 0, rc; + int32_t index, num_of_disks; + + num_of_disks = (int32_t)ARRAY_SIZE(ufs_disks); + for (index = 0; index < num_of_disks; index++) { + rc = disk_access_register(&ufs_disks[index].info); + if (rc < 0) { + LOG_ERR("Failed to register disk %s error %d", + ufs_disks[index].info.name, rc); + err = rc; + } + } + + return err; +} + +SYS_INIT(disk_ufs_register, POST_KERNEL, CONFIG_UFSDISK_INIT_PRIORITY); From dc82552c801f3f1f905c05aed4dc494b25f03559 Mon Sep 17 00:00:00 2001 From: Ajay Neeli Date: Wed, 12 Mar 2025 16:08:43 +0530 Subject: [PATCH 07/10] tests: subsys: ufs: Add UFS subsystem test Add a test for the UFS subsystem to verify the functionality of UFS stack implementation. The test performs basic read operations on a UFS card after initialization. Test components: - Init: Verifies the UFS host controller can detect card presence and initialize the UFS subsystem. - Configuration: Ensures that the UFS stack reports a valid configuration for the UFS card after initialization. Signed-off-by: Ajay Neeli --- tests/subsys/ufs/CMakeLists.txt | 8 +++ tests/subsys/ufs/README.txt | 13 ++++ tests/subsys/ufs/prj.conf | 9 +++ tests/subsys/ufs/src/main.c | 124 ++++++++++++++++++++++++++++++++ tests/subsys/ufs/testcase.yaml | 12 ++++ 5 files changed, 166 insertions(+) create mode 100644 tests/subsys/ufs/CMakeLists.txt create mode 100644 tests/subsys/ufs/README.txt create mode 100644 tests/subsys/ufs/prj.conf create mode 100644 tests/subsys/ufs/src/main.c create mode 100644 tests/subsys/ufs/testcase.yaml diff --git a/tests/subsys/ufs/CMakeLists.txt b/tests/subsys/ufs/CMakeLists.txt new file mode 100644 index 000000000000..ef70776deaf1 --- /dev/null +++ b/tests/subsys/ufs/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ufs_subsys_test) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/subsys/ufs/README.txt b/tests/subsys/ufs/README.txt new file mode 100644 index 000000000000..b52358840044 --- /dev/null +++ b/tests/subsys/ufs/README.txt @@ -0,0 +1,13 @@ +UFS Subsystem Test +################## + +This test is designed to verify the UFS subsystem stack implementation for UFS +devices. Note that this test will only perform basic reads from the UFS card +after initialization. It requires an UFS card be connected to the board to pass. + +* Init test: verify the UFS host controller can detect card presence, and + test the initialization flow of the UFS subsystem to verify that the stack + can correctly initialize an UFS card. + +* Configuration test: Verify that the UFS stack reports a valid configuration + for UFS card after initialization. diff --git a/tests/subsys/ufs/prj.conf b/tests/subsys/ufs/prj.conf new file mode 100644 index 000000000000..feeabca7c7b9 --- /dev/null +++ b/tests/subsys/ufs/prj.conf @@ -0,0 +1,9 @@ +CONFIG_TEST=y +CONFIG_ZTEST=y +CONFIG_LOG=y + +CONFIG_SCSI=y +CONFIG_UFS_STACK=y + +CONFIG_UFS_LOG_LEVEL_DBG=y +CONFIG_UFSHC_LOG_LEVEL_DBG=y diff --git a/tests/subsys/ufs/src/main.c b/tests/subsys/ufs/src/main.c new file mode 100644 index 000000000000..89ed6be8fccc --- /dev/null +++ b/tests/subsys/ufs/src/main.c @@ -0,0 +1,124 @@ +/* + * Copyright 2025 (c) Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define UFS_NODE DT_ALIAS(ufs0) + +BUILD_ASSERT(DT_NODE_HAS_STATUS(UFS_NODE, okay), "UFS node is disabled!"); + +static const struct device *const tst_ufshcdev = DEVICE_DT_GET_OR_NULL(UFS_NODE); +static struct ufs_host_controller *tst_ufshc; + +/* UFS card initialization */ +ZTEST(ufs_stack, test_0_init) +{ + int32_t ret; + + ret = ufs_init(tst_ufshcdev, &tst_ufshc); + zassert_equal(ret, 0, "UFS initialization failed: %d", ret); +} + +/* Read descriptor information from UFS card */ +ZTEST(ufs_stack, test_1_ioctl_desc) +{ + int32_t ret; + struct sg_io_req sg_req; + struct ufs_sg_req ufs_req; + struct ufs_qry_ioctl_req qry_ioctl_req; + uint8_t lun_enable = 0; + uint8_t test_lun = 0; + + sg_req.protocol = BSG_PROTOCOL_SCSI; + sg_req.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT; + sg_req.request = &ufs_req; + + sg_req.dxfer_dir = SG_DXFER_FROM_DEV; + sg_req.dxferp = (uint8_t *)(&lun_enable); + sg_req.dxfer_len = (uint32_t)sizeof(lun_enable); + + ufs_req.msgcode = (int32_t)UFS_SG_QUERY_REQ; + ufs_req.req_qry_ioctl = &qry_ioctl_req; + + qry_ioctl_req.ioctl_id = UFS_QRY_IOCTL_DESC; + qry_ioctl_req.desc.desc_id = UFSHC_UNIT_DESC_IDN; + qry_ioctl_req.desc.index = test_lun; + qry_ioctl_req.desc.param_offset = UFSHC_UD_PARAM_LU_ENABLE; + + ret = ufs_sg_request(tst_ufshc, &sg_req); + zassert_equal(ret, 0, "UFS IOCTL desc failed: %d", ret); + + TC_PRINT("Lun id:%d, lun_enable:%Xh\n", test_lun, lun_enable); +} + +/* Read attribute value from UFS card */ +ZTEST(ufs_stack, test_2_ioctl_attr) +{ + int32_t ret; + struct sg_io_req sg_req; + struct ufs_sg_req ufs_req; + struct ufs_qry_ioctl_req qry_ioctl_req; + uint32_t blun_attrval = 0U; + + sg_req.protocol = BSG_PROTOCOL_SCSI; + sg_req.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT; + sg_req.request = &ufs_req; + + sg_req.dxfer_dir = SG_DXFER_FROM_DEV; + sg_req.dxferp = &blun_attrval; + + ufs_req.msgcode = (int32_t)UFS_SG_QUERY_REQ; + ufs_req.req_qry_ioctl = &qry_ioctl_req; + + qry_ioctl_req.ioctl_id = UFS_QRY_IOCTL_ATTR; + qry_ioctl_req.attr.attr_id = UFSHC_BLUEN_ATTRID; + + ret = ufs_sg_request(tst_ufshc, &sg_req); + zassert_equal(ret, 0, "UFS IOCTL attr failed: %d", ret); + + TC_PRINT("bootlun_attrval:%Xh\n", blun_attrval); +} + +/* Read flag value from UFS card */ +ZTEST(ufs_stack, test_3_ioctl_flag) +{ + int32_t ret; + struct sg_io_req sg_req; + struct ufs_sg_req ufs_req; + struct ufs_qry_ioctl_req qry_ioctl_req; + bool flag_val = false; + + sg_req.protocol = BSG_PROTOCOL_SCSI; + sg_req.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT; + sg_req.request = &ufs_req; + + sg_req.dxfer_dir = SG_DXFER_FROM_DEV; + sg_req.dxferp = &flag_val; + + ufs_req.msgcode = (int32_t)UFS_SG_QUERY_REQ; + ufs_req.req_qry_ioctl = &qry_ioctl_req; + + qry_ioctl_req.ioctl_id = UFS_QRY_IOCTL_FLAG; + qry_ioctl_req.flag.flag_id = UFSHC_FDEVINIT_FLAG_IDN; + + ret = ufs_sg_request(tst_ufshc, &sg_req); + zassert_equal(ret, 0, "UFS IOCTL Flag failed: %d", ret); + + TC_PRINT("fdeviceinit_flag:%Xh\n", flag_val); +} + +/* UFS device setup */ +static void *ufs_test_setup(void) +{ + zassert_true(device_is_ready(tst_ufshcdev), "UFSHC device is not ready"); + + return NULL; +} + +ZTEST_SUITE(ufs_stack, NULL, ufs_test_setup, NULL, NULL, NULL); diff --git a/tests/subsys/ufs/testcase.yaml b/tests/subsys/ufs/testcase.yaml new file mode 100644 index 000000000000..dfadfed29872 --- /dev/null +++ b/tests/subsys/ufs/testcase.yaml @@ -0,0 +1,12 @@ +common: + depends_on: ufs + tags: + - subsys + - ufs +tests: + ufs.ufs_api: + harness: ztest + harness_config: + fixture: fixture_ufs + filter: dt_alias_exists("ufs0") + tags: ufs From 03e8dbcfab9c37669492e9e7eb93b6095ac567ec Mon Sep 17 00:00:00 2001 From: Ajay Neeli Date: Wed, 12 Mar 2025 16:16:11 +0530 Subject: [PATCH 08/10] tests: subsys: scsi: Add SCSI-UFS test Add a test to verify the SCSI subsystem's functionality with UFS devices. Test Components: - Init Test: Verifies SCSI device initialization via the UFS host controller. - IOCTL Test: Checks SCSI IOCTL operations using SG_IO. - R/W Test: Confirms data can be written to and read from the UFS device without errors. Requirements: - UFS device must be connected. - Write operations are disabled by default (can be enabled via configuration). Signed-off-by: Ajay Neeli --- tests/subsys/scsi/scsi_ufs/CMakeLists.txt | 8 ++ tests/subsys/scsi/scsi_ufs/Kconfig | 12 +++ tests/subsys/scsi/scsi_ufs/README.txt | 16 +++ tests/subsys/scsi/scsi_ufs/prj.conf | 9 ++ tests/subsys/scsi/scsi_ufs/src/main.c | 115 ++++++++++++++++++++++ tests/subsys/scsi/scsi_ufs/testcase.yaml | 14 +++ 6 files changed, 174 insertions(+) create mode 100644 tests/subsys/scsi/scsi_ufs/CMakeLists.txt create mode 100644 tests/subsys/scsi/scsi_ufs/Kconfig create mode 100644 tests/subsys/scsi/scsi_ufs/README.txt create mode 100644 tests/subsys/scsi/scsi_ufs/prj.conf create mode 100644 tests/subsys/scsi/scsi_ufs/src/main.c create mode 100644 tests/subsys/scsi/scsi_ufs/testcase.yaml diff --git a/tests/subsys/scsi/scsi_ufs/CMakeLists.txt b/tests/subsys/scsi/scsi_ufs/CMakeLists.txt new file mode 100644 index 000000000000..524d6c944f81 --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(scsi_subsys_test) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/subsys/scsi/scsi_ufs/Kconfig b/tests/subsys/scsi/scsi_ufs/Kconfig new file mode 100644 index 000000000000..7d77a7517063 --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/Kconfig @@ -0,0 +1,12 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "SCSI UFS test" + +config TEST_SCSI_UFS_RW + bool "SCSI UFS RW Tests (DANGEROUS)" + help + To perform Writing to UFS device as part of SCSI RW Tests. + This will overwrite existing data on UFS device. + +source "Kconfig.zephyr" diff --git a/tests/subsys/scsi/scsi_ufs/README.txt b/tests/subsys/scsi/scsi_ufs/README.txt new file mode 100644 index 000000000000..ffafa07d6391 --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/README.txt @@ -0,0 +1,16 @@ +SCSI-UFS Subsystem Test +####################### + +The Test is designed to verify the SCSI subsystem stack implementation for UFS +devices. It requires a UFS Device be connected to the board to pass. + +* Init test: Initialize the SCSI Device handle through UFS host controller. + +* IOCTL test: Verify the SCSI IOCTL operations based on SG_IO. + +Warning: Ensure that you have configured the test to allow writes to the UFS +device. By default, test prevents write to avoid data corruption. Please refer +to the configuration options in testcase to enable write operations if needed. + +* R/W Test: Write data to the SCSI Device and verify that it can be read back +without errors. diff --git a/tests/subsys/scsi/scsi_ufs/prj.conf b/tests/subsys/scsi/scsi_ufs/prj.conf new file mode 100644 index 000000000000..e5d0cd0b6a06 --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/prj.conf @@ -0,0 +1,9 @@ +CONFIG_TEST=y +CONFIG_ZTEST=y +CONFIG_LOG=y + +CONFIG_SCSI=y +CONFIG_UFS_STACK=y + +CONFIG_SCSI_LOG_LEVEL_DBG=y +CONFIG_UFS_LOG_LEVEL_DBG=y diff --git a/tests/subsys/scsi/scsi_ufs/src/main.c b/tests/subsys/scsi/scsi_ufs/src/main.c new file mode 100644 index 000000000000..c951bb71c462 --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/src/main.c @@ -0,0 +1,115 @@ +/* + * Copyright 2025 (c) Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define UFS_NODE DT_ALIAS(ufs0) + +BUILD_ASSERT(DT_NODE_HAS_STATUS(UFS_NODE, okay), "UFS node is disabled!"); + +#define SCSI_TEST_LUN 0 + +static struct scsi_host_info *tst_shost; +static struct scsi_device *tst_sdev; + +static const struct device *const tst_ufshcdev = DEVICE_DT_GET_OR_NULL(UFS_NODE); +static struct ufs_host_controller *tst_ufshc; + +#if IS_ENABLED(CONFIG_TEST_SCSI_UFS_RW) +#define SCSI_TEST_SECTOR_COUNT 3 +#define SCSI_TEST_MAX_SECTOR_SIZE 4096 +#define SCSI_TEST_BUF_SIZE (SCSI_TEST_SECTOR_COUNT * SCSI_TEST_MAX_SECTOR_SIZE) +static uint8_t wr_buf[SCSI_TEST_BUF_SIZE] __aligned(CONFIG_UFSHC_BUFFER_ALIGNMENT); +static uint8_t rd_buf[SCSI_TEST_BUF_SIZE] __aligned(CONFIG_UFSHC_BUFFER_ALIGNMENT); +#endif /* CONFIG_TEST_SCSI_UFS_RW */ + +/* Verify that SCSI Device Handle is Initialized */ +ZTEST(scsi_stack, test_0_init) +{ + tst_sdev = scsi_device_lookup_by_host(tst_shost, SCSI_TEST_LUN); + zassert_not_null(tst_sdev, "SCSI Device for SCSI_TEST_LUN is NULL"); +} + +/* Verify SCSI cmds using IOCTL */ +ZTEST(scsi_stack, test_1_scsi_cmd) +{ + int32_t rc; + + if (tst_sdev == NULL) { + ztest_test_skip(); + return; + } + + rc = scsi_ioctl(tst_sdev, SCSI_IOCTL_TEST_UNIT_READY, NULL); + zassert_equal(rc, 0, "SCSI_CMD - TUR failed: %d", rc); +} + +/* Verify SCSI Generic (SG) IOCTL */ +ZTEST(scsi_stack, test_2_scsi_sgio) +{ + int32_t rc; + struct sg_io_req req; + uint8_t tur_cmd[] = {SCSI_TST_U_RDY, 0, 0, 0, 0, 0}; + + if (tst_sdev == NULL) { + ztest_test_skip(); + return; + } + + req.protocol = BSG_PROTOCOL_SCSI; + req.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; + req.request = &tur_cmd[0]; + req.request_len = (uint32_t)sizeof(tur_cmd); + req.dxfer_dir = (int32_t)PERIPHERAL_TO_PERIPHERAL; + req.dxferp = NULL; + rc = scsi_ioctl(tst_sdev, SG_IO, &req); + zassert_equal(rc, 0, "SCSI_IOCTL - SGIO - TUR failed: %d", rc); +} + +#if IS_ENABLED(CONFIG_TEST_SCSI_UFS_RW) +/* Verify that SCSI stack can read/write to UFS card */ +ZTEST(scsi_stack, test_3_scsi_rw) +{ + int32_t rc; + int32_t block_addr = 0; + + if (tst_sdev == NULL) { + ztest_test_skip(); + return; + } + + memset(wr_buf, 0xAD, SCSI_TEST_BUF_SIZE); + memset(rd_buf, 0, SCSI_TEST_BUF_SIZE); + + rc = scsi_write(tst_sdev, block_addr, SCSI_TEST_SECTOR_COUNT, wr_buf); + zassert_equal(rc, 0, "Write SCSI failed: %d", rc); + rc = scsi_read(tst_sdev, block_addr, SCSI_TEST_SECTOR_COUNT, rd_buf); + zassert_equal(rc, 0, "Read SCSI failed: %d", rc); + zassert_mem_equal(wr_buf, rd_buf, SCSI_TEST_BUF_SIZE, + "Read of erased area was not zero"); +} +#endif /* CONFIG_TEST_SCSI_UFS_RW */ + +/* + * Scsi UFS Device Setup + * This setup must run first, to ensure UFS card is initialized. + */ +static void *scsi_test_setup(void) +{ + int32_t rc; + + zassert_true(device_is_ready(tst_ufshcdev), "UFSHC device is not ready"); + rc = ufs_init(tst_ufshcdev, &tst_ufshc); + zassert_equal(rc, 0, "UFS initialization failed: %d", rc); + tst_shost = tst_ufshc->host; + + return NULL; +} + +ZTEST_SUITE(scsi_stack, NULL, scsi_test_setup, NULL, NULL, NULL); diff --git a/tests/subsys/scsi/scsi_ufs/testcase.yaml b/tests/subsys/scsi/scsi_ufs/testcase.yaml new file mode 100644 index 000000000000..1ff04df7cd44 --- /dev/null +++ b/tests/subsys/scsi/scsi_ufs/testcase.yaml @@ -0,0 +1,14 @@ +common: + depends_on: + - scsi + tags: + - scsi + - ufs + filter: dt_alias_exists("ufs0") +tests: + scsi.ufs.scsi_basic: + depends_on: ufshc + scsi.ufs.scsi_rw: + depends_on: ufshc + extra_configs: + - CONFIG_TEST_SCSI_UFS_RW=y From 4c893d02d7ba3c0c311d09cb4b7eda4d62e286f8 Mon Sep 17 00:00:00 2001 From: Ajay Neeli Date: Mon, 17 Mar 2025 14:32:47 +0530 Subject: [PATCH 09/10] tests: drivers: disk: Add UFS to disk_access tests Extend disk_access test suite to support UFS disk driver. Signed-off-by: Ajay Neeli --- tests/drivers/disk/disk_access/src/main.c | 6 ++++-- tests/drivers/disk/disk_access/testcase.yaml | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/drivers/disk/disk_access/src/main.c b/tests/drivers/disk/disk_access/src/main.c index 0c36f1d6121e..479e98b40992 100644 --- a/tests/drivers/disk/disk_access/src/main.c +++ b/tests/drivers/disk/disk_access/src/main.c @@ -28,6 +28,8 @@ #define DISK_NAME_PHYS "NAND" #elif defined(CONFIG_NVME) #define DISK_NAME_PHYS "nvme0n0" +#elif defined(CONFIG_DISK_DRIVER_UFS) +#define DISK_NAME_PHYS "UFS" #elif defined(CONFIG_DISK_DRIVER_RAM) /* Since ramdisk is enabled by default on e.g. qemu boards, it needs to be checked last to not * override other backends. @@ -43,8 +45,8 @@ #define DISK_NAME DISK_NAME_PHYS #endif -/* Assume the largest sector we will encounter is 512 bytes */ -#define SECTOR_SIZE 512 +/* Assume the largest sector we will encounter is 4096 bytes */ +#define SECTOR_SIZE 4096 /* Sector counts to read */ #define SECTOR_COUNT1 8 diff --git a/tests/drivers/disk/disk_access/testcase.yaml b/tests/drivers/disk/disk_access/testcase.yaml index fea3bd706bfd..677619b58622 100644 --- a/tests/drivers/disk/disk_access/testcase.yaml +++ b/tests/drivers/disk/disk_access/testcase.yaml @@ -45,3 +45,5 @@ tests: platform_allow: - native_sim/native/64 - native_sim + drivers.disk.ufs: + filter: dt_compat_enabled("zephyr,ufs-disk") From 702bad48a5b7f0f2355ef4b91f595846b809fb40 Mon Sep 17 00:00:00 2001 From: Ajay Neeli Date: Mon, 17 Mar 2025 14:39:17 +0530 Subject: [PATCH 10/10] doc: peripherals: Add doc for UFSHC peripheral Add documentation for the UFS Host Controller (UFSHC) driver, which provides a generic interface for interacting with UFS host controller devices. It is used by the UFS subsystem to manage UFS-based storage devices. The documentation covers key aspects of the UFSHC, including initialization, transfer request handling, query requests, and configuration options, helping developers understand how to integrate and work with the UFS subsystem effectively. Signed-off-by: Ajay Neeli --- doc/hardware/peripherals/index.rst | 1 + doc/hardware/peripherals/ufshc.rst | 61 ++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 doc/hardware/peripherals/ufshc.rst diff --git a/doc/hardware/peripherals/index.rst b/doc/hardware/peripherals/index.rst index acb12942c236..6302a81bb60d 100644 --- a/doc/hardware/peripherals/index.rst +++ b/doc/hardware/peripherals/index.rst @@ -58,6 +58,7 @@ Peripherals stepper.rst smbus.rst uart.rst + ufshc.rst usbc_vbus.rst tcpc.rst tgpio.rst diff --git a/doc/hardware/peripherals/ufshc.rst b/doc/hardware/peripherals/ufshc.rst new file mode 100644 index 000000000000..0b469e572f22 --- /dev/null +++ b/doc/hardware/peripherals/ufshc.rst @@ -0,0 +1,61 @@ +.. _ufs_api: + +Universal Flash Storage (UFS) +############################# + +The UFS Host Controller driver api offers a generic interface for interacting +with a UFS host controller device. It is used by the UFS subsystem, and is +not intended to be directly used by the application. + +UFS Host Controller (UFSHC) Overview +************************************ + +The UFS host controller driver is based on SCSI Framework. UFSHC is a low +level device driver for UFS subsystem. The UFS subsystem acts as an interface +between SCSI and UFS Host controllers. + +The current UFS subsystem implementation supports the following functionality: + +UFS controller initialization +============================= + +Prepares the UFS Host Controller to transfer commands/responses between UFSHC +and UFS device. + +.. _ufs-init-api: + +Transfer requests +================= + +It handles SCSI commands from SCSI subsystem, form UPIUs and issues UPIUs to +the UFS Host controller. + +.. _ufs-scsi-api: + +Query Request +============= + +It handles query requests which are used to modify and retrieve configuration +information of the device. + +.. _ufs-query-api: + +Configuration Options +********************* + +Related configuration options: + +* :kconfig:option:`CONFIG_UFSHC` +* :kconfig:option:`CONFIG_UFS_STACK` +* :kconfig:option:`CONFIG_SCSI` + +API Reference +************* + +UFS subsystem APIs are provided by ufs.h + +.. doxygengroup:: ufs_interface + +UFS Host Controller (UFSHC) APIs are provided by ufshc.h + +.. doxygengroup:: ufshc_interface