高通xbl中监测ufs健康状态

原创不易,若帮到您,请点个赞!!!

说明:本文基于 西部数据INAND 8521A UFSV2.1 实现。

简介:

        高通平台UFS相关问题不好排查,为了增加UFS问题分析手段,并能在设备启动第一时间获取到UFS的健康状态,因此开发了在xbl中读取、打印UFS健康报告(Smart-report)的功能。

        在QNX系统中,ufs-utils-dev工具可通过系统调用方式读取UFS健康报告。我已下载到ufs-uitls-dev工具源码,并已移植到linux系统中正常运行。本文着重讲解xbl中如何实现读取UFS健康报告的。移植ufs-utils-dev步骤比较简单,不再赘述。

一、linux中ufs-utils-dev工具读取UFS健康报告

1、运行ufs-utils-dev工具

        ./ufs-utils vendor -i 1 -O 0x7d9c69 -g 1 -p /dev/sda

可以看到,累计上电初始化次数、累计非正常掉电次数等UFS健康数据。

2、附上ufs-utils-dev源码资料链接

https://download.csdn.net/download/hinewcc/88819544

二、xbl中读取UFS健康报告

1、xbl中代码框架

  • 黑色部分:读取并打印ufs健康报告
  • 红色部分:xbl中保存ufs健康报告

        如果只需要读取并打印ufs健康报告,很简单,只需要在ufs_api.c中定义ufs_report_result_get()读取ufs健康报告接口,在UFS.c中调用即可

2、代码分析

首先看下修改了哪些文件

​​​​​​​ 

2.1 QcomPkg/Library/UfsCommonLib/ufs_api.c

        新增读取、保存ufs健康状态信息API函数

2.1.1 ufs健康状态信息读取函数

        下面为新增的代码

/* 封装scsi命令读buf函数接口 */
int32 ufs_read_buf (struct ufs_handle *handle, uint8_t *buf, 
                            uint8_t mode, uint8_t buf_id, 
                            uint32_t offset, uint32_t len)
{
   int32 rc = EOK;

   STOR_PROFILE_START;

   // Check input parameter
   if (handle == NULL) {
      return -EINVAL;
   }

   // Check inuse flag
   if (handle->is_inuse != UFS_INUSE_TRUE ||
       handle->id_magic != UFS_HANDLE_MAGIC_ID) {
      return -EINVAL;
   }

   // Make sure the host controlle interface is ready
   if (handle->phc == NULL) {
      return -EINVAL;
   }

   /* SCSI命令整合并读取数据 */
   rc = ufs_scsi_read_buf (handle, buf, mode, buf_id, offset, len);

   STOR_PROFILE_END(UFS_PROFILE_READ_BIT_MASK, "UFS_READ", offset, len);

   return rc;
}

/* 解析ufs健康状态到UFS_REPORT_RESULT_T中 */
static void ufs_report_parse(uint8_t *buf, UFS_REPORT_RESULT_T *_pResult)
{
	int i;
	UFS_REPORT_FIELD_T *tmp;

	for (i = 0; i < _pResult->num; i++) {
		tmp = &_pResult->tField[i];

		if (tmp->width_byte == 1) {
         tmp->value = buf[tmp->offset];
		} else if (tmp->width_byte == (1 << 2)) {
         tmp->value = *(uint32_t *)&buf[tmp->offset];
		} else if (tmp->width_byte == (1 << 3)) {
			memcpy(_pResult->fw_rel_time, &buf[tmp->offset], FW_REL_TIME_LEN);
		} else if (tmp->width_byte == FW_REL_DATE_LEN) {
			memcpy(_pResult->fw_rel_date, &buf[tmp->offset], FW_REL_DATE_LEN);
		}
	}
}

/* debug打印ufs健康状态信息 */
static void ufs_report_print(char *str, uint8_t *buf, UFS_REPORT_RESULT_T *_pResult)
{
	int i;
	UFS_REPORT_FIELD_T *tmp;
	char fw_rel_date[FW_REL_DATE_LEN + 1] = {0};
	char fw_rel_time[FW_REL_TIME_LEN + 1] = {0};

	for (i = 0; i < _pResult->num; i++) {
		tmp = &_pResult->tField[i];

		if (tmp->width_byte == 1) {
			DEBUG ((EFI_D_ERROR, "%a [Byte offset 0x%x]: %a = 0x%x\n",
				str,
				tmp->offset,
				tmp->name,
				buf[tmp->offset]));
		} else if (tmp->width_byte == (1 << 2)) {
			DEBUG ((EFI_D_ERROR, "%a [Byte offset 0x%x]: %a = 0x%x\n",
				str,
				tmp->offset,
				tmp->name,
				*(uint32_t *)&buf[tmp->offset]));
		} else if (tmp->width_byte == (1 << 3)) {
			memcpy(fw_rel_time, &buf[tmp->offset],
				FW_REL_TIME_LEN);
			DEBUG ((EFI_D_ERROR, "%a [Byte offset 0x%x]: %a = %a\n",
				str,
				tmp->offset,
				tmp->name,
				fw_rel_time));
		} else if (tmp->width_byte == FW_REL_DATE_LEN) {
			memcpy(fw_rel_date, &buf[tmp->offset],
				FW_REL_DATE_LEN);
			DEBUG ((EFI_D_ERROR, "%a [Byte offset 0x%x]: %a = %a\n",
				str,
				tmp->offset,
				tmp->name,
				fw_rel_date));
		} else {
			DEBUG ((EFI_D_ERROR, "%a [Byte offset 0x%x]: %a Wrong Width = %d",
				str,
				tmp->offset,
				tmp->name,
				tmp->width_byte));
		}
	}
}

/* 读取UFS健康状态 */
#define UFS_BLOCK_SIZE  512
//#define UFS_REPORT_DEBUG		//打印ufs健康状态信息
int ufs_report_result_get(struct ufs_handle *_pHandle, UFS_REPORT_RESULT_T *_pResult)
{
   INT32  rc = 0; 
   uint8 buf[UFS_BLOCK_SIZE] = {0};

    /* 1.scsi命令读取ufs健康状态信息 */
   rc = ufs_read_buf (_pHandle, buf, 1, 1, 0x7d9c69, UFS_BLOCK_SIZE);
   if (UFS_EOK != rc) {
      DEBUG ((EFI_D_ERROR, "ufs_scsi_read_buf with return value of %d\n", rc));
      return -1;
   }

   /* 2.解析读取到的信息,并存入UFS_REPORT_RESULT_T结构体 */
   ufs_report_parse((uint8_t *)buf, _pResult);

#ifdef UFS_REPORT_DEBUG
   /* 3.debug打印读取到的ufs健康状态信息 */
   ufs_report_print("Device Report:", (uint8_t *)buf, _pResult);
#endif

   return 0;
}
  • 定义 UFS_REPORT_DEBUG 宏后,读取到的ufs健康报告会通过串口打印出来。
  • ufs_read_buf() 函数实参是根据资料的手册中 READ BUFFER 命令设置的

  • 读取ufs健康状态信息会用到UFS_REPORT_RESULT_T结构体,解析后的数据也会存入UFS_REPORT_RESULT_T。结构体定义在 ufs_api.h 中,如下:
#define FW_REL_DATE_LEN 12
#define FW_REL_TIME_LEN 8

typedef enum {
	BYTE_SIZE	= 1,
	WORD_SIZE	= 2,
	DOUBLE_WORD_SIZE = 4
}FIELD_WIDTH_E;

typedef struct {
	char *name;					//状态项名称
	int offset;					//对应读取buf中的偏移
	FIELD_WIDTH_E width_byte;	//占用字节数
   int value;					//解析后得到的值
   int bSave;					//是否保存,TRUE:保存,FALSE:不保存
}UFS_REPORT_FIELD_T;

#define UFS_REPORT_RESULT_NUM 44
typedef struct {
   char fw_rel_date[FW_REL_DATE_LEN + 1];    //固件版本编译日期
   char fw_rel_time[FW_REL_TIME_LEN + 1];    //固件版本编译时间
   UFS_REPORT_FIELD_T tField[UFS_REPORT_RESULT_NUM];	//ufs健康状态所有项,共44个
   int num;									//状态项数量,共44个
}UFS_REPORT_RESULT_T;

ufs健康信息44个项的缺省值定义:

UFS_REPORT_FIELD_T g_tUfsReportField_default[UFS_REPORT_RESULT_NUM] = {
/* 名称					读取buf中的偏移 	占时字节数 值(缺省0)	是否保存 */
	{"AverageEraseEnh",				0,	1 << 2,		0,		FALSE},
	{"AverageEraseTypeA",			4,	1 << 2,		0,		FALSE},
	{"AverageEraseTypeB",			8,	1 << 2,		0,		FALSE},
	{"ReadReclaimCountTypeC",		12,	1 << 2, 	0,		FALSE},
	{"ReadReclaimCountTypeA",		16, 1 << 2, 	0,		FALSE},
	{"ReadReclaimCountTypeB",		20, 1 << 2, 	0,		FALSE},
	{"BadBlockManufactory",			24, 1 << 2, 	0,		FALSE},
	{"BadBlockRuntimeTypeC",		28, 1 << 2, 	0,		FALSE},
	{"BadBlockRuntimeTypeA",		32, 1 << 2, 	0,		FALSE},
	{"BadBlockRuntimeTypeB",		36, 1 << 2, 	0,		FALSE},
	{"FieldFWUpdatesCount",			40, 1 << 2, 	0,		FALSE},
	{"FWReleaseDate",				44, FW_REL_DATE_LEN, 0, FALSE},
	{"FWReleaseTime",				56, 1 << 3, 	0,		FALSE},
	{"CumulativeHostWriteDataSize", 64, 1 << 2, 	0,		FALSE},
	{"NumVccVoltageDropsOccur",		68, 1 << 2, 	0,		TRUE},		//保存
	{"NumVccVoltageDroopsOccur",	72, 1 << 2, 	0,		FALSE},
	{"NumFailuresRecover",			76, 1 << 2, 	0,		FALSE},
	{"TotalRecoveryOperation",		80, 1 << 2, 	0,		FALSE},
	{"CumulativeSSLCWritePayload",	84, 1 << 2, 	0,		FALSE},
	{"CumulativeSSLCBigFileWritePayload", 88, 1 << 2, 0, 	FALSE},
	{"NumberTimesSSLCBigFileOperated", 92, 1 << 2, 	0, 		FALSE},
	{"AverageEraseSSLCBigModeCycles", 96, 1 << 2, 	0, 		FALSE},
	{"CumulativeInitCount",			100, 1 << 2, 	0, 		TRUE},		//保存
	{"MaxEraseCyclesTypeC",			104, 1 << 2, 	0,		FALSE},
	{"MaxEraseCyclesTypeA",			108, 1 << 2, 	0, 		FALSE},
	{"MaxEraseCyclesTypeB",			112, 1 << 2, 	0, 		FALSE},
	{"MinEraseCyclesTypeC",			116, 1 << 2, 	0, 		FALSE},
	{"MinEraseCyclesTypeA",			120, 1 << 2, 	0, 		FALSE},
	{"MinEraseCyclesTypeB",			124, 1 << 2, 	0, 		FALSE},
	{"PreEOLWarningTypeC",			152, 1 << 2, 	0, 		FALSE},
	{"PreEOLWarningTypeB",			156, 1 << 2, 	0, 		FALSE},
	{"UncorrectErrCorrectionCode",	160, 1 << 2, 	0, 		FALSE},
	{"CurrentTemperature",			164, 1 << 2, 	0, 		FALSE},
	{"MinTemperature",				168, 1 << 2, 	0, 		FALSE},
	{"MaxTemperature",				172, 1 << 2, 	0, 		FALSE},
	{"EnrichedDeviceHealthTypeC",	180, 1 << 2, 	0, 		FALSE},
	{"EnrichedDeviceHealthTypeB",	184, 1 << 2, 	0, 		FALSE},
	{"CurrentPowerMode",			191, 1, 		0, 		FALSE},
	{"EnrichedDeviceHealthTypeA",	192, 1 << 2, 	0, 		FALSE},
	{"PreEOLWarningTypeB",			196, 1 << 2, 	0, 		FALSE},
	{"NumIOVoltDroopsOccurrences",	200, 1 << 2, 	0, 		FALSE},
	{"CumulativeHostReadDataSize",	204, 1 << 2, 	0, 		FALSE},
	{"BitFlipDetectionCounter",		208, 1 << 2, 	0, 		FALSE},
	{"BitFlipCorrectionCounter",	212, 1 << 2, 	0, 		FALSE}
};

可以看到,当我们需要新增UFS某个健康状态项保存时,只用修改 bSave 值为 TRUE 即可。

2.1.2 ufs健康状态信息保存函数(若只打印不保存,则不用定义此函数
/* UFS健康状态保存 */
void ufs_report_result_save(UFS_REPORT_RESULT_T *_pResult)
{
   int i;
   UFS_REPORT_FIELD_T *pTmp;
   char SaveBuf[96] = {0};

   /* 轮询44个ufs健康状态项 */
   for (i = 0; i < _pResult->num; i++) {
      pTmp = &_pResult->tField[i];

      /* 当bSave为TRUE时,会将名称和值保存 */
      if (pTmp->bSave == TRUE) {
         snprintf(SaveBuf, 95, "%s : 0x%x", pTmp->name, pTmp->value);
         //调用数据保存接口(主要介绍如何读取ufs健康报告,保存接口自己写)
      }
   }
}

        到这里,最关键的UFS健康读取函数 ufs_report_result_get() 已经定义好了。调用后,根据项目需求直接打印数据或者保存。

2.2 QcomPkg/Drivers/UFSDxe/UFS.c

        UFS.c 是xbl中自带的一个UFS驱动,UFSDxeInitialize是它的入口函数。 函数会轮询lun,当lun为0时,拿到handle句柄调用 ufs_report_result_get() 去读取ufs健康状态即可。

2.2.1 读取ufs健康状态

        lun为0 时读取ufs健康状态,存到 s_tURResult 即可,代码在下面介绍。

2.2.2 定义GUID

  • QcomPkg/QcomPkg.dec[Protocols] 中定义 gEfiUfsReportResultProtocolGuid ,并设置为生成的GUID
[Protocols] 
  # Ufs Report Result            #新增
  gEfiUfsReportResultProtocolGuid =     { 0x1567b704, 0x54c6, 0x4041,{ 0x9e, 0xc0, 0x16, 0x34, 0x07, 0xe8, 0xa1, 0x61 } } 
  • QcomPkg/Drivers/UFSDxe/UFSDxe.inf[Protocols] 声明 gEfiUfsReportResultProtocolGuid

2.2.3 新建protocol,用作ufs健康状态结构体的传输通道(若只打印不保存,则无需新建protocol

   可以看到,Status = gBS->InstallMultipleProtocolInterfaces(&gEfiUfsReportResultProtocolGuid, &s_tURResult) 将 gEfiUfsReportResultProtocolGuid 和 s_tURResult 建立联系

/* ufs_api.h 头文件 */
#include <Library/UfsCommonLib/ufs_api.h>

/* 创建 UFS_REPORT_RESULT_T 结构体用于解析ufs健康状态和存储状态值 */
static UFS_REPORT_RESULT_T s_tURResult; 

/* UFS初始化接口,只需看ufs健康状态埋点相关内容 */
EFI_STATUS EFIAPI UFSDxeInitialize (
            IN EFI_HANDLE         ImageHandle,
            IN EFI_SYSTEM_TABLE   *SystemTable)
{
   /* 初始化UFS_REPORT_RESULT_T结构 */
   s_tURResult.num = UFS_REPORT_RESULT_NUM;		//44个状态项
   memcpy(s_tURResult.tField, g_tUfsReportField_default, sizeof(s_tURResult.tField));

    ... ...

   for (lun = 0; lun < total_luns_enabled; lun++)
   { 
      ... ...

      /* Initialize LUN */
      gUfsDevice[lun].DeviceHandle = ufs_open (0, lun);
      if (!gUfsDevice[lun].DeviceHandle) {
         gUfsDevice[lun].BlkIo.Media->MediaPresent = FALSE;
         continue;
      }
      ... ...

       /* lun为0 时读取ufs健康状态,存到 s_tURResult */
      if (lun == 0) {
         if (ufs_report_resul_get((struct ufs_handle *)gUfsDevice[lun].DeviceHandle, &s_tURResult)){
            DEBUG ((DEBUG_ERROR, "ufs device report fail\n"));
         }
      }

      /* Assume LUN0 and install the following protocols: */ 
      /* BlkIO */
      Status = gBS->InstallMultipleProtocolInterfaces (
         &gUfsDevice[lun].ClientHandle,  
         &gEfiBlockIoProtocolGuid, &gUfsDevice[lun].BlkIo, 
         &gEfiDevicePathProtocolGuid, &gUfsDevicePath[lun],
         &gEfiMemCardInfoProtocolGuid, &gUfsDevice[lun].CardInfo,
         &gEfiBlockIo2ProtocolGuid, &gUfsDevice[lun].BlkIo2,
         &gEfiEraseBlockProtocolGuid, &gUfsDevice[lun].EraseBlk,
         &gEfiStorageWpProtocolGuid, &gUfsDevice[lun].WriteProtect, 
          
	  /* 创建Protocol,用于传递ufs健康状态 */
         &gEfiUfsReportResultProtocolGuid, &s_tURResult,
         NULL
      );
      ASSERT_EFI_ERROR(Status);
   }
}

        到这里,ufs健康状态存在 s_tURResult 中,通过 gEfiUfsReportResultProtocolGuid 可以拿到。

        不管是在xbl中其他驱动,还是abl中,只需通过 gEfiUfsReportResultProtocolGuid 就能拿到 UFSDxeInitialize 函数中读取到的ufs健康报告了。

3、测试结果

        在 ufs_report_result_get() 函数前定义 UFS_REPORT_DEBUG 宏,读取ufs健康报告时就会解析并通过串口打印出来:

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值