此函数通过调用其他的函数,完成了scsi_debug模块主要的工作:接收和处理SCSI命令。对于真实的物理设备,在此接收到命令之后会发送给具体的物理设备,例如QLogic系列HBA卡驱动函数qla2xxx_queuecommand所示,完成了对于FC连接的检查之后进行数据发送。
rval = fc_remote_port_chkready(rport);
if (rval) {
cmd->result = rval;
ql_dbg(ql_dbg_io, vha, 0x3003,
"fc_remote_port_chkready failed for cmd=%p, rval=0x%x.\n",
cmd, rval);
goto qc24_fail_command;
}
…
if (atomic_read(&fcport->state) != FCS_ONLINE) {
if (atomic_read(&fcport->state) == FCS_DEVICE_DEAD ||
atomic_read(&base_vha->loop_state) == LOOP_DEAD) {
ql_dbg(ql_dbg_io, vha, 0x3005,
"Returning DNC, fcport_state=%d loop_state=%d.\n",
atomic_read(&fcport->state),
atomic_read(&base_vha->loop_state));
cmd->result = DID_NO_CONNECT << 16;
goto qc24_fail_command;
}
goto qc24_target_busy;
}
sp = qla2x00_get_new_sp(base_vha, fcport, cmd);
if (!sp)
goto qc24_host_busy;
rval = ha->isp_ops->start_scsi(sp);
if (rval != QLA_SUCCESS) {
ql_dbg(ql_dbg_io, vha, 0x3013,
"Start scsi failed rval=%d for cmd=%p.\n", rval, cmd);
goto qc24_host_busy_free_sp;
}
对于scsi_debug模块本身来说,没有真实的物理设备可以发送,因此在此模拟处理请求并返回结果就可以了,关于如何处理SCSI命令及返回值将在3.3中详述。
0xfae69100 : scsi_debug_queuecommand+0x0/0x0 [scsi_debug]
0xc06f311c : scsi_dispatch_cmd+0xdc/0x300 [kernel]
0xc06f9da6 : scsi_request_fn+0x396/0x690 [kernel]
0xc05fc407 : __blk_run_queue+0x27/0x30 [kernel]
0xc0613a84 : cfq_insert_request+0x2a4/0x640 [kernel]
0xc05f6b4a : elv_insert+0xaa/0x1b0 [kernel]
0xc05f6c99 : __elv_add_request+0x49/0xa0 [kernel]
0xc05ff681 : __make_request+0xe1/0x570 [kernel]
0xc05fe1f6 : generic_make_request+0x3a6/0x610 [kernel]
0xc05fe4d5 : submit_bio+0x75/0x110 [kernel]
0xc056f483 : submit_bh+0xf3/0x140 [kernel]
0xc057115b : __block_write_full_page+0x20b/0x3b0 [kernel]
0xc0571c69 : block_write_full_page_endio+0xa9/0xe0 [kernel]
0xc0571cb2 : block_write_full_page+0x12/0x20 [kernel]
0xc0575f1f : blkdev_writepage+0xf/0x20 [kernel]
0xc050601b : __writepage+0xb/0x30 [kernel]
0xc0506ded : write_cache_pages+0x13d/0x370 [kernel]
0xc050703f : generic_writepages+0x1f/0x30 [kernel]
0xc0507067 : do_writepages+0x17/0x30 [kernel]
0xc0568d8c : writeback_single_inode+0xbc/0x240 [kernel]
0xc056913b : writeback_sb_inodes+0xab/0x160 [kernel]
以上为scsi_debug模块在writeback写入数据情况下的调用栈,从中可以看出依次涉及到了linux kernel的vfs层,block层,scsi mid layer(scsi_dispatch_cmd)。
使用rmmod scsi_debug移除模块时,调用栈如下,从中可以看出remove device时需要调用注册总线的remove函数,同时涉及到了scsi upper layer(sd_remove),mid layer(scsi_dispatch_cmd)。
0xfae69100 : scsi_debug_queuecommand+0x0/0x0 [scsi_debug]
0xc06f311c : scsi_dispatch_cmd+0xdc/0x300 [kernel]
0xc06f9da6 : scsi_request_fn+0x396/0x690 [kernel]
0xc05fc4a9 : __generic_unplug_device+0x29/0x30 [kernel]
0xc0602dd8 : blk_execute_rq_nowait+0x68/0xd0 [kernel]
0xc0602ebd : blk_execute_rq+0x7d/0xe0 [kernel]
0xc06fb370 : scsi_execute+0xb0/0x130 [kernel]
0xc06fb59b : scsi_execute_req+0x8b/0x140 [kernel]
0xf9b8a60a : sd_sync_cache+0xba/0x100 [sd_mod]
0xf9b8a7fa : sd_shutdown+0x6a/0x130 [sd_mod]
0xf9b8aa1a : sd_remove+0x5a/0xa0 [sd_mod]
0xc06e36e2 : __device_release_driver+0x52/0xb0 [kernel]
0xc06e3800 : device_release_driver+0x20/0x40 [kernel]
0xc06e297c : bus_remove_device+0x7c/0xd0 [kernel]
0xc06e0a61 : device_del+0xf1/0x180 [kernel]
0xc06ff2f9 : __scsi_remove_device+0x99/0xa0 [kernel]
0xc06fbeff : scsi_forget_host+0x4f/0x60 [kernel]
0xc06f4a43 : scsi_remove_host+0x53/0xe0 [kernel]
0xfae64f84 : sdebug_driver_remove+0x24/0x80 [scsi_debug]
0xc06e36e2 : __device_release_driver+0x52/0xb0 [kernel]
0xc06e3800 : device_release_driver+0x20/0x40 [kernel]
-
scsi_debug_abort、scsi_debug_bus_reset、scsi_debug_device_reset、scsi_debug_host_reset
这是LLDD中定义的错误处理逻辑,如果不定义,则SCSI midlayer会提供自己的定义,这些函数将在kernel提供的scsi_ehX错误处理线程中执行,该线程定义和创建代码在drivers/scsi/host.c中:
struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
{
…
shost->ehandler = kthread_run(scsi_error_handler, shost,
"scsi_eh_%d", shost->host_no);
if (IS_ERR(shost->ehandler)) {
printk(KERN_WARNING "scsi%d: error handler thread failed to spawn, error = %ld\n",
shost->host_no, PTR_ERR(shost->ehandler));
goto fail_kfree;
}
…
scsi_debug在此提供这些函数主要是为增加全局的错误计数器,以scsi_debug_bus_reset为例:
++num_bus_resets;
if (SCpnt && ((sdp = SCpnt->device)) && ((hp = sdp->host))) {
sdbg_host = *(struct sdebug_host_info **)shost_priv(hp);
if (sdbg_host) {
list_for_each_entry(dev_info,
&sdbg_host->dev_info_list,
dev_list)
dev_info->reset = 1;
}
}
-
scsi_debug_biosparam
返回对于指定磁盘的bios参数,通常是由driver自己生成返回,实现如下:
buf = scsi_bios_ptable(bdev);
if (buf) {
res = scsi_partsize(buf, capacity,
&info[2], &info[0], &info[1]);
kfree(buf);
if (! res)
return res;
}
info[0] = sdebug_heads;
info[1] = sdebug_sectors_per;
info[2] = sdebug_cylinders_per;
return 0;
scsi_bios_ptable定义在drivers/scsi/scsicam.c中,功能是从指定的device的第一个sector上读取分区表内容,随后交给scsi_partsize完成分区表的创建,按照(柱面,磁头,扇区)的格式存储在info[2],info[0],info[1]中,若不能正常获取分区表信息,则赋予全局默认值。
除了sdebug_driver_template提供的与scsi mid layer的结合之外,scsi_debug也提供了一个虚拟的总线以绑定虚拟出来的scsi设备,如下:
static struct bus_type pseudo_lld_bus = {
.name = "pseudo",
.match = pseudo_lld_bus_match,
.probe = sdebug_driver_probe,
.remove = sdebug_driver_remove,
};
其中pseudo_lld_bus_match恒返回1,sdebug_driver_probe中在总线pseudo_lld_bus上注册了scsi设备sdebug_driver_template,sdebug_driver_remove则从总线上移除了该设备。do_create_driverfs_files和do_remove_driverfs_files负责对于/sys/bus/pseudo/drivers/scsi_debug下文件的创建和删除,与sdebug_xx_store和sdebug_xx_show组合起来完成了将scsi_debug模块信息导出和动态修改的功能。linux的scsi模块提供了超时机制,定义在schedule_resp函数中,如下:
static int schedule_resp(struct scsi_cmnd * cmnd,
struct sdebug_dev_info * devip,
done_funct_t done, int scsi_result, int delta_jiff)
{
…
if (delta_jiff <= 0) {
if (cmnd)
cmnd->result = scsi_result;
if (done)
done(cmnd);
return 0;
} else {
unsigned long iflags;
int k;
struct sdebug_queued_cmd * sqcp = NULL;
spin_lock_irqsave(&queued_arr_lock, iflags);
for (k = 0; k < scsi_debug_max_queue; ++k) {
sqcp = &queued_arr[k];
if (! sqcp->in_use)
break;
}
if (k >= scsi_debug_max_queue) {
spin_unlock_irqrestore(&queued_arr_lock, iflags);
printk(KERN_WARNING "scsi_debug: can_queue exceeded\n");
return 1; /* report busy to mid level */
}
sqcp->in_use = 1;
sqcp->a_cmnd = cmnd;
sqcp->scsi_result = scsi_result;
sqcp->done_funct = done;
sqcp->cmnd_timer.function = timer_intr_handler;
sqcp->cmnd_timer.data = k;
sqcp->cmnd_timer.expires = jiffies + delta_jiff;
add_timer(&sqcp->cmnd_timer);
spin_unlock_irqrestore(&queued_arr_lock, iflags);
if (cmnd)
cmnd->result = 0;
return 0;
}
在此设置了sdebug_queued_cmd中封装的scsi command及定时器timer,同时设置了回调函数done_funct,如果delta_jiff小于零则直接执行传入的回调以通知mid layer。