实战UFS驱动开发:手把手教你实现SCSI命令到UPIU的转换(基于Qcom平台)

实战UFS驱动开发:手把手教你实现SCSI命令到UPIU的转换(基于Qcom平台) 实战UFS驱动开发从SCSI命令到UPIU的完整转换流程解析在移动设备存储架构中UFSUniversal Flash Storage已成为高端SoC的首选方案。作为开发者理解SCSI命令到UFS协议单元UPIU的转换机制是进行底层驱动调试和性能优化的关键基础。本文将基于Qualcomm平台和Kernel 5.10环境通过可复现的实验步骤带你深入这一转换过程的核心实现。1. 实验环境搭建与工具链配置1.1 硬件准备与内核配置开发UFS驱动需要特定的硬件支持环境开发平台推荐使用Qualcomm Snapdragon 8系列开发套件如QRD845这些平台通常提供完整的JTAG调试接口和UFS协议分析工具链内核配置确保内核包含以下关键配置选项CONFIG_SCSI_UFSHCDy CONFIG_SCSI_UFSHCD_PLATFORMy CONFIG_SCSI_UFS_CRYPTOy CONFIG_SCSI_UFS_QCOMy CONFIG_TRACEPOINTSy提示在编译内核前建议执行make menuconfig确认上述选项已启用特别是与平台相关的Qualcomm特定驱动1.2 调试工具安装UFS开发需要一组专用调试工具工具名称安装方法主要功能ufs-utilsapt-get install ufs-utilsUFS设备状态监控和基础操作trace-cmd内核源码tools/perf目录编译安装内核事件跟踪crash utility从Android源码prebuilt目录获取内存转储分析JTag调试器需硬件配套软件寄存器级调试# 验证工具链是否正常工作 $ sudo ufs-utils -l $ trace-cmd list -e ufshcd*2. SCSI命令处理流程深度解析2.1 从块层到SCSI层的转换当文件系统发起I/O请求时内核经过以下关键路径转换块层处理blk_mq_run_hw_queue() → blk_mq_dispatch_rq_list() → scsi_queue_rq()这个调用链将通用块请求转换为SCSI特定的命令描述块CDBSCSI命令构造在scsi_queue_rq()中完成的关键操作cmd blk_mq_rq_to_pdu(req); // 从request获取SCSI命令结构 scsi_init_command(); // 初始化命令控制块 scsi_setup_cmnd(); // 设置CDB字段注意在ARM64架构上SCSI CDB的字节序需要特别注意大端模式和小端模式的转换可能影响命令解析2.2 UFS驱动入口处理SCSI命令通过host模板中的queuecommand回调进入UFS驱动层static struct scsi_host_template ufshcd_driver_template { .queuecommand ufshcd_queuecommand, // 其他回调函数... };在ufshcd_queuecommand()中的关键转换步骤LRB准备lrbp hba-lrb[tag]; // 获取对应tag的逻辑请求块 lrbp-cmd cmd; // 关联SCSI命令 lrbp-lun ufshcd_scsi_to_upiu_lun(cmd-device-lun); // LUN转换UPIU构造ufshcd_comp_scsi_upiu()完成核心转换逻辑ufshcd_prepare_req_desc_hdr(lrbp); // 构造UPIU头部 ufshcd_prepare_utp_scsi_cmd_upiu(lrbp); // 填充命令字段3. UPIU构造与传输实战3.1 UPIU数据结构详解UPIUUFS Protocol Information Unit是UFS协议的核心数据结构主要包含以下部分字段名称长度(bytes)说明Transaction Type1区分命令/响应/任务管理等类型Flags1方向标志/数据缓冲指示等LUN1逻辑单元号Task Tag1请求标识符Command Set1SCSI/ATA/UFS等命令集类型CDB16SCSI命令描述块// 内核中的UPIU头部定义简化 struct utp_upiu_header { __be32 dword_0; // 包含Transaction Type等字段 __be32 dword_1; // 包含LUN/Task Tag等信息 __be32 dword_2; // 命令集相关字段 };3.2 关键转换函数实现ufshcd_prepare_utp_scsi_cmd_upiu()中的核心逻辑CDB复制memcpy(upiu-sc.cdb, lrbp-cmd-cmnd, UFS_CDB_SIZE);将SCSI命令块完整复制到UPIU的对应区域数据方向设置if (scsi_bufflen(lrbp-cmd) 0) { upiu-flags lrbp-cmd-sc_data_direction DMA_FROM_DEVICE ? UPIU_FLAGS_READ : UPIU_FLAGS_WRITE; }加密处理如果启用if (ufshcd_is_crypto_enabled(hba)) { ufshcd_prepare_lrbp_crypto(lrbp-cmd-request, lrbp); }4. 调试技巧与异常处理4.1 Tracepoint跟踪实战内核提供了多个关键tracepoint用于调试UFS命令流# 启用所有UFS相关tracepoint trace-cmd start -e ufshcd_* -e scsi_*典型输出分析示例ufshcd_queue_command: host0 tag5 cmd0xffffffc0c4e2d800 ufshcd_send_command: hba0xffffffc0c4d38000 tag5 doorbell0x20 ufshcd_complete_transfer_req: hba0xffffffc0c4d38000 tag54.2 常见问题排查清单现象可能原因排查方法命令超时Doorbell寄存器未触发JTAG读取REG_UTP_TRANSFER_REQ_DOOR_BELL数据校验错误PRD表配置异常检查ufshcd_map_sg()的sg映射结果LUN无法识别SCSI到UPIU的LUN转换错误验证ufshcd_scsi_to_upiu_lun()返回值加密命令失败密钥未正确配置检查hba-crypto_capabilities4.3 JTAG寄存器级调试当遇到硬件级问题时需要通过JTAG读取关键寄存器Doorbell寄存器jtag read_memory 0x01D80000 4 # UFS控制器基地址0x38检查对应tag位是否被正确置位中断状态寄存器jtag read_memory 0x01D80020 4 # REG_INTERRUPT_STATUS偏移确认中断是否被正确触发在实际项目中我们发现Qualcomm平台特有的一个坑点某些型号的UFS控制器需要在发送命令前手动清除中断状态位否则可能导致命令卡死。这个行为在标准UFS协议中并未要求属于平台特定行为。