从代码中可以看出,函数会对传入的scsi command中的prot_sdb包含的scatterlist中每个page中的数据块调用kmap_atomic映射到线性地址,然后生成校验码,生成校验码的具体算法由scsi_debug_guard指定,如果设置为1,则直接调用了lib下的ip_compute_csum生成校验,否则调用SBC标准中规定的CRC计算方法,具体可见SBC标准第4.22.4.2小节。
返回resp_write函数:
write_lock_irqsave(&atomic_rw, iflags);
ret = do_device_access(SCpnt, devip, lba, num, 1);
if (scsi_debug_unmap_granularity)
map_region(lba, num);
write_unlock_irqrestore(&atomic_rw, iflags);
if (-1 == ret)
return (DID_ERROR << 16);
else if ((ret < (num * scsi_debug_sector_size)) &&
(SCSI_DEBUG_OPT_NOISE & scsi_debug_opts))
printk(KERN_INFO "scsi_debug: write: cdb indicated=%u, "
" IO sent=%d bytes\n", num * scsi_debug_sector_size, ret);
return 0;
}
do_device_access函数定义如下,从中可以看出,函数实现相当简洁,根据是否为write调用fetch_to_dev_buffer 或者fill_from_dev_buffer,即如果为写,将数据拷贝入虚拟出来的设备的内存中,否则从设备的内存中读取数据。
static int do_device_access(struct scsi_cmnd *scmd,
struct sdebug_dev_info *devi,
unsigned long long lba, unsigned int num, int write)
{
int ret;
unsigned long long block, rest = 0;
int (*func)(struct scsi_cmnd *, unsigned char *, int);
func = write ? fetch_to_dev_buffer : fill_from_dev_buffer;
block = do_div(lba, sdebug_store_sectors);
if (block + num > sdebug_store_sectors)
rest = block + num - sdebug_store_sectors;
ret = func(scmd, fake_storep + (block * scsi_debug_sector_size),
(num - rest) * scsi_debug_sector_size);
if (!ret && rest)
ret = func(scmd, fake_storep, rest * scsi_debug_sector_size);
return ret;
}
在完成resp_write之后,针对之前fault injection中设置的标志位返回相关的错误码,例如ABORTED_COMMAND和ILLEGAL_REQUEST,否则正常返回。
4 总结
到此可以看出scsi_debug是在完全参照着SCSI相关标准实现的,对于其他真实的物理设备的驱动也同样需要实现此处scsi_debug实现的各种“填表”工作,在此之上实现自己的业务逻辑。SCSI标准在主机和外设之间定义了一个标准的接口,外设只需做到scsi_debug的工作,就不需要为设备的兼容性和移植性担忧,这也正是各大厂商始终推动着SCSI标准不断发展的原因。
5 参考资料
-
Information Storage and Management. EMC.
-
Guide to the implementation of SCSI in Linux 2.6.X kernel.
-
SCSI标准(SAM、SPC、SBC)。