一、问题描述

1问题简要描述

\source\kernel\disk
Disk子系统内核部分在内核中加载磁盘时,发送命令到磁盘获取磁盘的容量错误。
初步定位发现,命令发送到驱动后,驱动返回的数据是正确的,同时disk子系统内核态向disk管理用户态代码上报的容量错误,说明错误出现在disk子系统的内核态代码。

2定位信息

日志信息

CDB: 25 00 00 00 00 00 00 00 00 00 00 00				//read capacity10命令
satReadCapacity10: lastLba is 0x575466ef 1465149167
satReadCapacity10: LBA 0 is 0x57 87
satReadCapacity10: LBA 1 is 0x54 84
satReadCapacity10: LBA 2 is 0x66 102
satReadCapacity10: LBA 3 is 0xef 239			//驱动获取的磁盘容量是0x57 54 66 ef
satReadCapacity10: Default Block Length is 512
satReadCapacity10 0x57 0x54 0x66 0xef 0x0 0x0 0x2 0x0 , tiDeviceHandle=ffffc20000305d40 tiIORequest=ffff81005a5bb468
/* 上面一段是驱动打印的信息 */
ODSP:MSG:DISK: CAPACITYP PAGE LEN:8
ODSP:MSG:DISK: SCSI: get disk capacity:0xfffffff0,*cap=0xfffffff0.//获取的容量变成了0xfffffff0
ODSP:DBG:DISK:7050:sd_revalidate_disk:640: disk media present,get disk capacity:0xfffffff0.
/* 上面这一段是disk 内核模块打印 */

二、定位方法

开始怀疑是disk 内核代码从驱动返回的缓冲区拷贝数据错误,所以在拷贝数据的地方添加打印,检查数据是否一致。

int get_read_capacity_page_10(struct odsp_scsi_cmnd *scmd, ses_pages_t* p_spage)
{
    int32_t result = S_OK;
    uint8_t cmd[16];
    ………….
    scmd->request_buffer = kmalloc(READ_CAPACITY_LEN_10,GFP_KERNEL);

   	……….
    scmd->request_bufflen = READ_CAPACITY_LEN_10;
……..
    result = odsp_scsi_execute_cmnd(scmd);
    ………

    p_spage->page = (char *)(scmd->request_buffer);
    
    p_spage->page_len = scmd->request_bufflen;
    
/* 添加打印检查从scsi命令缓冲返回的数据是否正确 */
	DBG_SEND(“SCSI:read capacity 10,buffer:\n”
” 0x%02x, 0x%02x, 0x%02x, 0x%02x,\n”
” 0x%02x, 0x%02x, 0x%02x, 0x%02x”,
p_spage->page[0], p_spage->page[1], p_spage->page[2], p_spage->page[3],
p_spage->page[4], p_spage->page[5], p_spage->page[6], p_spage->page[7]);

    return result;

}

从新加载后的打印信息,??????????出现异样!!!!!!!奇怪~~~~~~~~

CDB: 25 00 00 00 00 00 00 00 00 00 00 00
satReadCapacity10: lastLba is 0x575466ef 1465149167
satReadCapacity10: LBA 0 is 0x57 87
satReadCapacity10: LBA 1 is 0x54 84
satReadCapacity10: LBA 2 is 0x66 102
satReadCapacity10: LBA 3 is 0xef 239
satReadCapacity10: Default Block Length is 512
satReadCapacity10 0x57 0x54 0x66 0xef 0x0 0x0 0x2 0x0 , tiDeviceHandle=ffffc20000306ae0 tiIORequest=ffff81006e6b0468
ODSP:MSG:DISK: SCSI:read capacity 10,buffer:
0x57,0x54,0x66,0xffffffef,			//前3个字节是一致的,第4个字节怎么会是这样??
0x00,0x00,0x02,0x00
ODSP:MSG:DISK: CAPACITYP PAGE LEN:8
ODSP:MSG:DISK: SCSI: get disk capacity:0xfffffff0,*cap=0xfffffff0.

三、问题分析

1代码分析

回到代码发现代码中的缓存区的数据类型有问题。

typedef struct ses_pages
{
    char* page;					//这里为什么要用char呢?
    unsigned page_len;
} ses_pages_t; 

int get_read_capacity_page_10(struct odsp_scsi_cmnd *scmd, ses_pages_t* p_spage)
{
    int32_t result = S_OK;
    uint8_t cmd[16];
    ………….
    scmd->request_buffer = kmalloc(READ_CAPACITY_LEN_10,GFP_KERNEL);
   	……….
    scmd->request_bufflen = READ_CAPACITY_LEN_10;
……..
    result = odsp_scsi_execute_cmnd(scmd);
    ………
    p_spage->page = (char *)(scmd->request_buffer);	//这里将缓冲区的数据转换成了char
    p_spage->page_len = scmd->request_bufflen;
    /* 添加打印检查从scsi命令缓冲返回的数据是否正确 */
	DBG_SEND(“SCSI:read capacity 10,buffer:\n”
” 0x%02x, 0x%02x, 0x%02x, 0x%02x,\n”
” 0x%02x, 0x%02x, 0x%02x, 0x%02x”,
p_spage->page[0], p_spage->page[1], p_spage->page[2], p_spage->page[3],
p_spage->page[4], p_spage->page[5], p_spage->page[6], p_spage->page[7]);
    return result;
}

后面的代码又将缓冲区中的数据进行拼接,拼接时用到到类型强制转换

{
char capacitybuf[12];

diskinfo->disk_capacity =  1 + (uint64_t)(((uint32_t)capacitybuf[0] << 24) |
                             ((uint32_t)capacitybuf[1] << 16) |
                             ((uint32_t)capacitybuf[2] << 8)  |
                             ((uint32_t)capacitybuf[3]));	//将一个char型数据强制转换成了uint32_t
}

2问题定位

问题就处在的数据类型上,一个char型的数据被强制转换成uint32_t时进行了符号扩展。所以第4个字节的0xef被扩展成了0xffffffef,再经过上面算法的拼接最后磁盘的容量就变成了0xfffffff0。

四、修改方法

将该缓冲区的类型改为uint8_t,同时修改数据拼接的代码:修改后的代码

typedef struct ses_pages
{
    uint8_t* page;					//这里改为uint8_t
    unsigned page_len;
} ses_pages_t; 

int get_read_capacity_page_10(struct odsp_scsi_cmnd *scmd, ses_pages_t* p_spage)
{
    int32_t result = S_OK;
    uint8_t cmd[16];
    ………….
    scmd->request_buffer = kmalloc(READ_CAPACITY_LEN_10,GFP_KERNEL);
   	……….
    scmd->request_bufflen = READ_CAPACITY_LEN_10;
……..
    result = odsp_scsi_execute_cmnd(scmd);
    ………
    p_spage->page = (uint8_t *)(scmd->request_buffer);	//这里将缓冲区的数据转换成uint8_t
    p_spage->page_len = scmd->request_bufflen;
    
    return result;
}

{
uint8_t capacitybuf[12];

diskinfo->disk_capacity =  1 + (uint64_t)(((uint32_t)(capacitybuf[0]&0xff) << 24) |
                             ((uint32_t) (capacitybuf[1]&0xff)<< 16) |
                             ((uint32_t) (capacitybuf[2]&0xff)<< 8)  |
                             ((uint32_t) (capacitybuf[3]&0xff)));	//将要运算的部分取模
}

修改后的log

CDB: 25 00 00 00 00 00 00 00 00 00 00 00
satReadCapacity10: lastLba is 0x575466ef 1465149167
satReadCapacity10: LBA 0 is 0x57 87
satReadCapacity10: LBA 1 is 0x54 84
satReadCapacity10: LBA 2 is 0x66 102
satReadCapacity10: LBA 3 is 0xef 239
satReadCapacity10: Default Block Length is 512
satReadCapacity10 0x57 0x54 0x66 0xef 0x0 0x0 0x2 0x0 , tiDeviceHandle=ffffc20000306ae0 tiIORequest=ffff810002773068
ODSP:MSG:DISK: SCSI:read capacity 10,buffer:
0x57,0x54,0x66,0xef,
0x00,0x00,0x02,0x00
ODSP:MSG:DISK: CAPACITYP PAGE LEN:8
ODSP:MSG:DISK: SCSI: get disk capacity:0x575466f0,*cap=0x575466f0.

五、经验小结
问题分类
数据类型强制转换的问题。
数据运算没有取模保护。

问题影响
对于子系统来说是数据计算错误,但对于整个设备来说磁盘容量获取错误是致命的。

该问题有两个建议
1.对于从驱动返回的数据缓冲区最好使用无符号的数据类型,没有必要使用有符号的数据类型。
2.要注意数据类型的强制转换,特别是从短向长转换。
3.数据拼接时最好取模保护,避免不必要的数据影响。