一、问题描述
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.数据拼接时最好取模保护,避免不必要的数据影响。