函数名称:get_health_logs
函数功能:获取NVMe设备的健康信息日志
参数说明:
- struct spdk_nvme_ctrlr *ctrlr:指向NVMe控制器的指针
- struct health_entry *health:指向存储健康信息的结构体指针
返回值:int类型,成功返回0,失败返回负数错误码
函数详细解释:
该函数用于获取NVMe设备的健康信息日志。在NVMe设备中,有一个称为“SMART(Self-Monitoring, Analysis and Reporting Technology)”的技术,可以用于监测和报告设备的健康状况和其他相关信息。该函数通过NVMe控制器发送NVMe Admin命令获取健康信息,并将其存储在给定的结构体中。
函数流程:
首先检查输入参数是否有效,并分配命令和数据缓冲区。
构造NVMe Admin命令,设置命令的操作码、命名空间ID和CNS(Controller NVM Subsystem),并将命令发送给NVMe控制器。
如果命令发送成功,则等待命令完成及响应结果;否则返回错误码。
解析响应结果,将健康信息复制到给定的结构体中。
释放命令和数据缓冲区,返回0表示成功。
具体实现代码如下:
static int
get_health_logs(struct spdk_nvme_ctrlr *ctrlr, struct health_entry *health)
{
int rc;
struct spdk_nvme_cmd cmd = {};
struct spdk_nvme_health_information_page health_page = {};
void *buffer = NULL;
/* 检查输入参数 */
if (ctrlr == NULL || health == NULL) {
return -1;
}
/* 分配命令和数据缓冲区 */
buffer = spdk_zmalloc(sizeof(struct spdk_nvme_health_information_page),
SPDK_CACHE_LINE_SIZE, NULL, SPDK_ENV_SOCKET_ID_ANY,
SPDK_MALLOC_DMA);
if (buffer == NULL) {
SPDK_ERRLOG("Failed to allocate memory for health information page.\n");
return -ENOMEM;
}
/* 构造NVMe Admin命令 */
memset(&cmd, 0, sizeof(cmd));
cmd.opc = SPDK_NVME_OPC_GET_LOG_PAGE; //获取日志页操作码
cmd.nsid = 0xFFFFFFFF; //特殊值,表示所有的命名空间
cmd.cdw10 = SPDK_NVME_LOG_HEALTH_INFORMATION; //健康信息日志页
cmd.cdw11 = 0x00000000; //保留字段
cmd.cdw12 = 0x00000000; //保留字段
cmd.cdw13 = ((sizeof(health_page) / sizeof(uint32_t)) - 1); //日志页大小
/* 发送NVMe Admin命令 */
rc = spdk_nvme_ctrlr_cmd_admin_raw(ctrlr, &cmd, buffer, sizeof(health_page),
get_health_logs_completion, health);
if (rc != 0) {
SPDK_ERRLOG("Failed to send NVMe Admin command: rc=%d\n", rc);
spdk_free(buffer);
return rc;
}
/* 等待命令完成及响应结果 */
spdk_thread_poll(ctrlr->adminq_thread, 0, 0);
spdk_free(buffer);
return 0;
}
其中,spdk_nvme_ctrlr_cmd_admin_raw()函数用于发送NVMe Admin命令,并等待命令完成及响应结果。该函数的第5个参数为命令完成回调函数,用于在命令完成时调用。
健康信息日志页的格式如下:
struct spdk_nvme_health_information_page {
uint16_t temperature; //温度
uint8_t avail_spare; //可用空间
uint8_t spare_thresh; //空间阈值
uint8_t percent_used; //使用率百分比
uint8_t reserved[26]; //保留字段
struct spdk_nvme_health_information_controller_data ctrlr_data; //控制器数据
struct spdk_nvme_health_information_namespace_data ns_data[]; //命名空间数据
} __attribute__((packed));
其中,控制器数据和命名空间数据的结构体定义如下:
struct spdk_nvme_health_information_controller_data {
uint8_t critical_warning; //关键警告状态
uint8_t temperature_1; //温度1
uint8_t temperature_2; //温度2
uint8_t temperature_3; //温度3
uint16_t media_errors; //媒体错误数
uint8_t num_err_log_entries; //错误日志记录数
uint32_t warning_temp_time; //警告温度时间
uint32_t critical_temp_time; //关键温度时间
uint16_t temp_sensor1; //温度传感器1
uint16_t temp_sensor2; //温度传感器2
uint16_t temp_sensor3; //温度传感器3
uint8_t reserved[302]; //保留字段
} __attribute__((packed));
struct spdk_nvme_health_information_namespace_data {
uint64_t nsze; //命名空间大小
uint64_t ncap; //命名空间容量
uint64_t nuse; //命名空间使用量
uint64_t ndlbws; //命名空间数据读带宽
uint64_t ndlbs; //命名空间数据读请求数量
uint64_t ndlwbs; //命名空间数据写带宽
uint64_t ndlws; //命名空间数据写请求数量
uint64_t nvmcap[2]; //NVM总能力
uint8_t reserved[40]; //保留字段
} __attribute__((packed));
解析健康信息日志页的代码如下:
static void
get_health_logs_completion(void *arg, const struct spdk_nvme_cpl *cpl)
{
struct health_entry *health = arg;
struct spdk_nvme_health_information_page *health_page = health->buf;
struct spdk_nvme_health_information_controller_data *ctrlr_data = &health_page->ctrlr_data;
struct spdk_nvme_health_information_namespace_data *ns_data;
uint32_t nsid;
int i;
/* 检查响应结果 */
if (spdk_nvme_cpl_is_error(cpl)) {
SPDK_ERRLOG("Failed to get health information page: cdw0=%#x\n", cpl->cdw0);
health->rc = -EIO;
return;
}
/* 复制健康信息到结构体中 */
health->temperature = health_page->temperature;
health->avail_spare = health_page->avail_spare;
health->spare_thresh = health_page->spare_thresh;
health->percent_used = health_page->percent_used;
ctrlr_data->critical_warning &= 0x70; //保留关键警告状态的高4位
health->ctrlr_warning = ctrlr_data->critical_warning;
health->temp_sensor1 = ctrlr_data->temp_sensor1;
health->temp_sensor2 = ctrlr_data->temp_sensor2;
health->temp_sensor3 = ctrlr_data->temp_sensor3;
health->media_errors = ctrlr_data->media_errors;
health->num_err_log_entries = ctrlr_data->num_err_log_entries;
for (i = 0; i < SPDK_NVME_MAX_NS_SUPPORTED; i++) {
ns_data = &health_page->ns_data[i];
nsid = spdk_nvme_ctrlr_get_ns(ctrlr, i + 1)->id;
if (nsid == 0) {
break;
}
health->ns[i].id = nsid;
health->ns[i].size = ns_data->nsze;
health->ns[i].capacity = ns_data->ncap;
health->ns[i].used = ns_data->nuse;
health->ns[i].data_read_bandwidth = ns_data->ndlbws;
health->ns[i].data_read_requests = ns_data->ndlbs;
health->ns[i].data_write_bandwidth = ns_data->ndlwbs;
health->ns[i].data_write_requests = ns_data->ndlws;
}
}
该函数首先检查NVMe Admin命令的响应结果,如果出错则返回错误码。否则,将健康信息日志页中的温度、可用空间、空间阈值和使用率复制到结构体中,并解析控制器数据和命名空间数据,将其存储在相应的结构体中。
最后,调用该函数时,需要传入一个health_entry类型的结构体指针,用于存储健康信息。其中,结构体定义如下:
struct health_entry {
void *buf; //缓冲区指针
int rc; //返回码
uint16_t temperature; //温度
uint8_t avail_spare; //可用空间
uint8_t spare_thresh; //空间阈值
uint8_t percent_used; //使用率百分比
uint8_t ctrlr_warning; //控制器警告状态
uint16_t temp_sensor1; //温度传感器1
uint16_t temp_sensor2; //温度传感器2
uint16_t temp_sensor3; //温度传感器3
uint16_t media_errors; //媒体错误数
uint8_t num_err_log_entries; //错误日志记录数
struct {
uint32_t id; //命名空间ID
uint64_t size; //命名空间大小
uint64_t capacity; //命名空间容量
uint64_t used; //命名空间使用量
uint64_t data_read_bandwidth; //命名空间数据读带宽
uint64_t data_read_requests; //命名空间数据读请求数量
uint64_t data_write_bandwidth; //命名空间数据写带宽
uint64_t data_write_requests; //命名空间数据写请求数量
} ns[SPDK_NVME_MAX_NS_SUPPORTED]; //命名空间数组
} __attribute__((aligned(SPDK_CACHE_LINE_SIZE)));