Zephyr UFS Driver Implementation

This page provides details on the implementation of the UFS Zephyr driver.

Zephyr - Boot up Process (Arm Cortex-R5 processor)

Zephyr boot-up flow on the Arm Cortex-R5: Power ON enters the Reset Handler / startup code (which relocates the vector table, clears BSS and interrupts, configures stack and MPU), then runs Kernel Init (which performs HW initialization based on the device tree and priority and starts the kernel scheduler), and finally the application.

Zephyr boot-up process on the Arm Cortex-R5 processor.

Zephyr Project - Directory Structure

Zephyr project top-level directory tree: zephyrproject/ contains bootloader/, modules/ (with fs/fatfs and hal/), tools/, and zephyr/ (with subsys/, drivers/, modules/fatfs, soc/, board/).

Top-level directory structure of a zephyrproject/ tree.

Zephyr - Subsystem Layer (Generic Driver Stack)

Zephyr subsystem layer diagram listing the FS (Filesystem), Disk, SD, and USB subsystems sitting atop a generic, vendor-independent driver stack.

Zephyr generic subsystem layer.

Zephyr - Driver Layer (Vendor Specific Drivers)

Zephyr driver layer diagram showing the filesystem-related Disk, Flash, and SDHC drivers atop a vendor-independent driver stack.

Zephyr vendor-independent driver layer.

Zephyr - UFS Driver (Proposed Directory Structure)

Proposed Zephyr UFS driver directory layout: zephyr/subsys/ hosts the SCSI layer (scsi/) and the generic UFS stack (ufs/) for UFS initialization, UIC commands, and UPIU requests, while zephyr/drivers/ hosts the vendor-specific host controller driver (ufshc/).

Proposed directory layout for the Zephyr UFS driver.

Zephyr - UFS Driver Device Tree

Ufs Node

ufs0: ufs@f10b0000 {
    compatible = "amd,versal2-ufshc";
    status = "disabled";
    reg =   <0xf10b0000 0x10000>,
            <0xf1060000 0x2000>,
            <0xf1250000 0x100>,
            <0xf1260000 0x100>;
    interrupts = <GIC_SPI 234 IRQ_TYPE_LEVEL IRQ_DEFAULT_PRIORITY>;
    clocks = <&ufs_core_clk>;
    clock-names = "core_clk";
};

amd,versal2-ufshc.yaml

# Copyright (c) 2024 Advanced Micro Devices, Inc.
# SPDX-License-Identifier: Apache-2.0

description: |
  Amd Versal Gen2 UFS Host Controller.

  Example usage:
      ufs@f10b0000 {
        compatible = "amd,versal2-ufs";
        reg = <0xf10b0000 0x1000>;
        interrupts = <GIC_SPI 234 IRQ_TYPE_LEVEL_HIGH>;
        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-ufshc"

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

  interrupts:
    required: true
    description: Interrupts used by the UFS Host Controller.

  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: |
      - "ufshc-rst" - Reset signal for the UFS Host Controller
      - "ufsphy-rst" - Reset signal for the UFS PHY

Ufs Disk Node

ufs_disk0 {
    compatible = "zephyr,ufs-disk";
    lun = <0>;
    disk-name = "UFS";
    status = "okay";
};

Zephyr - UFS Driver Kconfig

  • CONFIG_AMD_VERSAL2_UFSHC — AMD Versal2 UFS Driver

  • CONFIG_UFSHC - UFS Driver

  • CONFIG_UFS_STACK - UFS Subsystem Stack

  • CONFIG_SCSI - SCSI Subsystem Stack

  • CONFIG_DYNAMIC_INTERRUPTS - Runtime interrupt configure

  • CONFIG_EVENTS - Posting ISR Events

  • CONFIG_HEAP_MEM_POOL_SIZE - For Heap Memory Allocation

  • CONFIG_CACHE_MANAGEMENT - For cached related operations

SCSI Layer

  • To support SCSI commands used for UFS

    • READ, WRITE, TEST UNIT READY

<subsys><scsi><src>

/* SCSI subsys file */


/**
 * struct scsi_cmd - information about a SCSI command to be processed
 * @cmd:     Command descriptor block (CDB)
 * @lun:     Target LUN (Logical Unit Number)
 * @cmdlen:  Length of the command
 * @datalen: Total length of data to be transferred
 * @pdata:   Pointer to data to be transferred
 * @dma_dir: Direction of data transfer (e.g., read/write)
 */
struct scsi_cmd {
    uint8_t cmd[MAX_CDB_SIZE];
    uint8_t lun;
    uint8_t cmdlen;
    uint64_t datalen;
    uint8_t *pdata;
    uint8_t dma_dir;
} __packed;

/**
 * struct scsi_host_info - Information about a SCSI host controller
 * @sdevices: List of connected SCSI devices
 * @ops:      Pointer to the SCSI operations structure
 * @hostdata: Pointer to host-specific data
 * @parent:   Parent device associated with the host
 */
struct scsi_host_info {
    sys_slist_t sdevices;
    const struct scsi_ops *ops;
    void *hostdata;
    struct device *parent;
};

/**
 * struct scsi_device - Information about a SCSI device
 * @node:        Node in the list of SCSI devices
 * @host:        Pointer to the associated SCSI host controller
 * @lun:         LUN ID for the device
 * @sector_size: Size of sectors in bytes
 * @capacity:    Total capacity of the device in blocks
 */
struct scsi_device {
    sys_snode_t node;
    struct scsi_host_info *host;
    uint8_t lun;
    uint32_t sector_size;
    uint32_t capacity;
};

/**
 * struct scsi_ops - Operations for managing SCSI commands
 *
 * @exec: Function to execute a SCSI command
 */
struct scsi_ops {
    /**
     * exec() - Execute a SCSI command
     *
     * @sdev:   SCSI device handle
     * @cmd:    SCSI command to execute
     *
     * @return: 0 on success, negative value on error
     */
    int32_t (*exec)(struct scsi_device *sdev, struct scsi_cmd *cmd);
};

/**
 * struct sg_io_req - Structure to represent a SCSI Generic (SG) I/O request
 * @protocol:         SCSI protocol type
 * @subprotocol:      Sub-protocol type (0 for SCSI command, 1 for SCSI transport)
 * @request:          Pointer to the SCSI CDB or transport layer request
 * @request_len:      Length of the request in bytes
 * @response:         Pointer to the response buffer
 * @max_response_len: Maximum length of the response buffer
 * @dxfer_dir:        Data transfer direction
 * @dxfer_len:        Length of the data to transfer
 * @dxferp:           Pointer to the data to be transferred
 */
struct sg_io_req {
    uint32_t protocol;
    uint32_t subprotocol;
    void *request;
    uint32_t request_len;
    void *response;
    uint32_t max_response_len;
    int32_t dxfer_dir;
    uint32_t dxfer_len;
    void *dxferp;
};

/* Public APIs exposed from SCSI layer */
/**
 * scsi_ioctl - Perform an IOCTL operation on a SCSI device
 *
 * @sdev: Pointer to the SCSI device
 * @cmd:  IOCTL command
 * @arg:  Arguments for the IOCTL command
 *
 * @return: 0 on success, negative value on error
 */
int32_t scsi_ioctl(struct scsi_device *sdev, int32_t cmd, void *arg);

/**
 * scsi_host_alloc - Allocate memory for a SCSI host controller
 *
 * @sops: Pointer to the SCSI operations structure
 *
 * @return: Pointer to the allocated scsi_host_info structure, or NULL on failure
 */
struct scsi_host_info *scsi_host_alloc(const struct scsi_ops *sops);

UFS Subsys Layer

  • To support UFS Bare-metal driver fnctionality in Zephyr OS

    • Host Controller Initialization

    • UIC Cmd, UTP Transfer Requests UPIU

<subsys><ufs><src>

/* UFS Subsys file */

/**
 * struct ufs_host_controller - UFS host controller private structure
 * @dev: Pointer to the driver device handle.
 * @mmio_base: Base address for the UFSHCI memory-mapped I/O registers.
 * @is_initialized: Flag indicating if the host controller has been initialized.
 * @irq: IRQ number for the UFS host controller.
 * @is_cache_coherent: Flag indicating whether the UFS controller supports cache coherency.
 * @ucdl_base_addr: Pointer to the UFS Command Descriptor base address.
 * @utrdl_base_addr: Pointer to the UTP Transfer Request Descriptor base address.
 * @xfer_req_depth: Maximum depth of the Transfer Request Queue supported by the controller.
 * @outstanding_xfer_reqs: Bitfield representing outstanding transfer requests.
 * @irq_event: Event object used to signal interrupt handling.
 * @ufs_lock: Mutex to synchronize access to UFS driver resources.
 * @dev_info: Structure holding information about the UFS device.
 * @host: Pointer to the SCSI host structure associated with the UFS controller.
 */
struct ufs_host_controller {
    struct device *dev;
    mem_addr_t mmio_base;
    bool is_initialized;
    uint32_t irq;
    uint8_t is_cache_coherent;
    struct ufshc_xfer_cmd_desc *ucdl_base_addr;
    struct ufshc_xfer_req_desc *utrdl_base_addr;
    uint32_t xfer_req_depth;
    uint32_t outstanding_xfer_reqs;
    struct k_event irq_event;
    struct k_mutex ufs_lock;
    struct ufs_dev_info dev_info;
    struct scsi_host_info *host;
};

/**
 * struct ufs_qry_ioctl_req - UFS IOCTL request
 * @ioctl_id: IOCTL identifier for the request
 * @attr: UFS attribute query data
 * @flag: UFS flag query data
 * @desc: UFS descriptor query data
 *
 * 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;
    union {
        struct ufs_qry_ioctl_attr attr;
        struct ufs_qry_ioctl_flag flag;
        struct ufs_qry_ioctl_desc desc;
    };
};

/**
 * struct ufs_sg_req - UFS SCSI Generic (SG) request
 * @msgcode: Message code for the request
 * @req_qry_ioctl: Pointer to the UFS query IOCTL request data
 *
 * This structure is used for representing SCSI Generic requests,
 * querying UFS attributes, flags, or descriptors.
 */
struct ufs_sg_req {
    int32_t msgcode;
    struct ufs_qry_ioctl_req *req_qry_ioctl;
};


/* Public APIs exposed from UFS Layer */
/**
 * ufs_init - Initialize the UFS host controller
 * @ufshc_dev: Pointer to the device structure for the UFS host controller
 * @ufshc: Pointer to the UFS host controller structure that will be initialized
 *
 * Return: 0 on success, negative error code on failure
 */
int32_t ufs_init(const struct device *ufshc_dev, struct ufs_host_controller ufshc);

/
 * ufs_sg_request - Handle a SCSI Generic (SG) request for UFS
 * @ufshc: Pointer to the UFS host controller structure
 * @arg: Argument for the request, which could be a UFS query or other data
 *
 * Return: 0 on success, negative error code on failure
 */
int32_t ufs_sg_request(struct ufs_host_controller *ufshc, void *arg);

UFS Driver Layer

  • Vendor Specific Driver API Implementation

  • Device Tree Instantiation

driver.h

/* Driver APIs which are hooked for UFS host controller */

/**
 * @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.
     */
    int32_t (*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.
     */
    int32_t (*link_startup_notify)(const struct device *dev, uint8_t status);
};

driver.src

#define DT_DRV_COMPAT amd_versal2_ufshc

/**
 * struct ufshc_versal2_config - Configuration for the Versal2 UFS Host Controller.
 * @mmio_base: Base address for the UFS controller memory-mapped I/O.
 * @core_clk_rate: UFS core clock rate in Hz.
 * @irq_id: IRQ line for the UFS controller interrupt.
 * @reg_iou_slcr: IOU SLCR register address for UFS configuration.
 * @reg_efuse_cache: eFuse cache register address.
 * @reg_ufs_crp: UFS CRP register address.
 */
struct ufshc_versal2_config {
    mem_addr_t mmio_base;
    uint32_t core_clk_rate;
    uint32_t irq_id;
    mem_addr_t reg_iou_slcr;
    mem_addr_t reg_efuse_cache;
    mem_addr_t reg_ufs_crp;
};

/**
 * struct ufshc_versal2_data - Runtime data for the Versal2 UFS Host Controller.
 * @ufshc: UFS host controller structure.
 * @rx_att_comp_val_l0: Receive AFE compensation value for lane 0.
 * @rx_att_comp_val_l1: Receive AFE compensation value for lane 1.
 * @rx_ctle_comp_val_l0: Receive CTLE compensation value for lane 0.
 * @rx_ctle_comp_val_l1: Receive CTLE compensation value for lane 1.
 */
struct ufshc_versal2_data {
    struct ufs_host_controller ufshc;
    uint32_t rx_att_comp_val_l0;
    uint32_t rx_att_comp_val_l1;
    uint32_t rx_ctle_comp_val_l0;
    uint32_t rx_ctle_comp_val_l1;
};

/* Driver APIs. to register */
static const struct ufshc_driver_api 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 Versal2
 * @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)

Zephyr - UFS Driver - Filesystem Example - Flow

Zephyr UFS filesystem example flow: from Zephyr boot-up, the device tree compatibles 'amd,versal2-ufshc' and 'zephyr,ufs-disk' call ufshc_versal2_init and disk_ufs_register; the application then mounts the filesystem and the fs_open / fs_read / fs_write / disk_ioctl calls flow through disk_ufs_* into the SCSI write / read / IOCTL layer and the underlying scsi_request and ufs_request transport.

Zephyr UFS driver - filesystem example end-to-end flow.

Zephyr - UFS Driver - Raw API Example - Flow

Zephyr UFS raw-API example flow: after kernel and post-kernel init, the device tree compatibles 'amd,versal2-ufshc' and 'zephyr,ufs-disk' call ufshc_versal2_init and disk_ufs_register; the application then opens the disk through disk_init(UFS) and disk_ufs_init, and disk_write / disk_read / disk_ioctl calls are forwarded straight into the SCSI and UFS request layer.

Zephyr UFS driver - raw API example end-to-end flow.

Zephyr - UFS Driver - Initialization Flow

UFS driver initialization sequence: ufs_init invokes ufshc_host_initialize, which allocates UFS transfer / command / descriptor memory and registers the ISR, runs ufshc_link_startup (invoking the variant ufshc_versal2_phy_init and the DME_LINKSTARTUP / post-link-startup notify), brings the host operational, then ufshc_card_initialize sends NOP_OUT UPIU and queries the fDeviceInit flag, ufshc_get_lun_info populates LUN information, and finally ufs_scsi_bind registers the SCSI host through scsi_host_alloc and scsi_add_lun_host.

Zephyr UFS driver initialization sequence.

Zephyr - UFS Driver - RAW UFS requests

UFS RAW-mode request flow: ufs_request dispatches through req-msgcode (nop_out, query, task management) into ufshcd_exec_raw_upiu_cmd.

Zephyr UFS driver - raw UFS UPIU request dispatch.