中断例程的确切类型依赖于SCSI控制器所连接到的外围总线的类型(PCI,
ISA等等)。
SIM驱动程序的中断例程运行在中断级别splcam上。因此应当在驱动
程序中使用splcam()
来同步中断例程与驱动程序
剩余部分的活动(对于能察觉多处理器的驱动程序,事情更要有趣,但
此处我们忽略这种情况)。本文档中的伪代码简单地忽略了同步问题。
实际代码一定不能忽略它们。一个较笨的办法就是在进入其他例程的
入口点处设splcam()
,并在返回时将它复位,从而
用一个大的临界区保护它们。为了确保中断级别总是会被恢复,可以定义
一个包装函数,如:
static void
xxx_action(struct cam_sim *sim, union ccb *ccb)
{
int s;
s = splcam();
xxx_action1(sim, ccb);
splx(s);
}
static void
xxx_action1(struct cam_sim *sim, union ccb *ccb)
{
... process the request ...
}
这种方法简单而且健壮,但它存在的问题是中断可能会被阻塞相对
很长的事件,这会对系统性能产生负面影响。另一方面,
spl()
函数族有相当高的额外开销,因此大量
很小的临界区可能也不好。
中断例程处理的情况和其中细节严重依赖于硬件。我们考虑
“典型(typical)”情况。
首先,我们检查总线上是否遇到了SCSI复位(可能由同一SCSI总线上
的另一SCSI控制器引起)。如果这样我们丢弃所有入队的和断开连接的
请求,报告事件并重新初始化我们的SCSI控制器。初始化期间控制器
不会发出另一个复位,这对我们十分重要,否则同一SCSI总线上的两个控制器
可能会一直来回地复位下去。控制器致命错误/挂起的情况可以在同一
地方进行处理,但这可能需要发送RESET信号到SCSI总线来复位与SCSI
设备的连接状态。
int fatal=0;
struct ccb_trans_settings neg;
struct cam_path *path;
if( detected_scsi_reset(softc)
|| (fatal = detected_fatal_controller_error(softc)) ) {
int targ, lun;
struct xxx_hcb *h, *hh;
/* 丢弃所有入队的CCB */
for(h = softc->first_queued_hcb; h != NULL; h = hh) {
hh = h->next;
free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET);
}
/* 要报告的协商的干净值 */
neg.bus_width = 8;
neg.sync_period = neg.sync_offset = 0;
neg.valid = (CCB_TRANS_BUS_WIDTH_VALID
| CCB_TRANS_SYNC_RATE_VALID | CCB_TRANS_SYNC_OFFSET_VALID);
/* 丢弃所有断开连接的CCB和干净协商 */
for(targ=0; targ <= OUR_MAX_SUPPORTED_TARGET; targ++) {
clean_negotiations(softc, targ);
/* report the event if possible */
if(xpt_create_path(&path, /*periph*/NULL,
cam_sim_path(sim), targ,
CAM_LUN_WILDCARD) == CAM_REQ_CMP) {
xpt_async(AC_TRANSFER_NEG, path, &neg);
xpt_free_path(path);
}
for(lun=0; lun <= OUR_MAX_SUPPORTED_LUN; lun++)
for(h = softc->first_discon_hcb[targ][lun]; h != NULL; h = hh) {
hh=h->next;
if(fatal)
free_hcb_and_ccb_done(h, h->ccb, CAM_UNREC_HBA_ERROR);
else
free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET);
}
}
/* 报告事件 */
xpt_async(AC_BUS_RESET, softc->wpath, NULL);
/* 重新初始化可能花很多时间,这种情况下应当由另一中断发信号
* 指示初始化否完成,或在超时时检查 - 但为了简单我们假设
* 初始化真的很快
*/
if(!fatal) {
reinitialize_controller_without_scsi_reset(softc);
} else {
reinitialize_controller_with_scsi_reset(softc);
}
schedule_next_hcb(softc);
return;
}
如果中断不是由控制器范围的条件引起的,则很可能当前硬件控制块
出现了问题。依赖于硬件,可能有非HCB相关的事件,此处我们指示不考虑
它们。然后我们分析这个HCB发生了什么:
struct xxx_hcb *hcb, *h, *hh;
int hcb_status, scsi_status;
int ccb_status;
int targ;
int lun_to_freeze;
hcb = get_current_hcb(softc);
if(hcb == NULL) {
/* 或者丢失(stray)的中断,或者某些东西严重错误,
* 或者这是硬件相关的某些东西
*/
进行必要的处理;
return;
}
targ = hcb->target;
hcb_status = get_status_of_current_hcb(softc);
首先我们检查HCB是否完成,如果完成我们就检查返回的SCSI状态。
if(hcb_status == COMPLETED) {
scsi_status = get_completion_status(hcb);
然后看这个状态是否与REQUEST SENSE命令有关,如果有关则简单
地处理一下它。
if(hcb->flags & DOING_AUTOSENSE) {
if(scsi_status == GOOD) { /* autosense成功 */
hcb->ccb->ccb_h.status |= CAM_AUTOSNS_VALID;
free_hcb_and_ccb_done(hcb, hcb->ccb, CAM_SCSI_STATUS_ERROR);
} else {
autosense_failed:
free_hcb_and_ccb_done(hcb, hcb->ccb, CAM_AUTOSENSE_FAIL);
}
schedule_next_hcb(softc);
return;
}
否则命令自身已经完成,把更多注意力放在细节上。如果这个CCB
没有禁用auto-sense并且命令连同sense数据失败,则运行REQUEST SENSE
命令接收那些数据。
hcb->ccb->csio.scsi_status = scsi_status;
calculate_residue(hcb);
if( (hcb->ccb->ccb_h.flags & CAM_DIS_AUTOSENSE)==0
&& ( scsi_status == CHECK_CONDITION
|| scsi_status == COMMAND_TERMINATED) ) {
/* 启动auto-SENSE */
hcb->flags |= DOING_AUTOSENSE;
setup_autosense_command_in_hcb(hcb);
restart_current_hcb(softc);
return;
}
if(scsi_status == GOOD)
free_hcb_and_ccb_done(hcb, hcb->ccb, CAM_REQ_CMP);
else
free_hcb_and_ccb_done(hcb, hcb->ccb, CAM_SCSI_STATUS_ERROR);
schedule_next_hcb(softc);
return;
}
属于协商事件的一个典型事情:从SCSI目标(回答我们的协商企图或
由目标发起的)接收到的协商消息,或目标无法协商(拒绝我们的协商消息
或不回答它们)。
switch(hcb_status) {
case TARGET_REJECTED_WIDE_NEG:
/* 恢复到8-bit总线 */
softc->current_bus_width[targ] = softc->goal_bus_width[targ] = 8;
/* 报告事件 */
neg.bus_width = 8;
neg.valid = CCB_TRANS_BUS_WIDTH_VALID;
xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg);
continue_current_hcb(softc);
return;
case TARGET_ANSWERED_WIDE_NEG:
{
int wd;
wd = get_target_bus_width_request(softc);
if(wd <= softc->goal_bus_width[targ]) {
/* 可接受的回答 */
softc->current_bus_width[targ] =
softc->goal_bus_width[targ] = neg.bus_width = wd;
/* 报告事件 */
neg.valid = CCB_TRANS_BUS_WIDTH_VALID;
xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg);
} else {
prepare_reject_message(hcb);
}
}
continue_current_hcb(softc);
return;
case TARGET_REQUESTED_WIDE_NEG:
{
int wd;
wd = get_target_bus_width_request(softc);
wd = min (wd, OUR_BUS_WIDTH);
wd = min (wd, softc->user_bus_width[targ]);
if(wd != softc->current_bus_width[targ]) {
/* 总线宽度改变了 */
softc->current_bus_width[targ] =
softc->goal_bus_width[targ] = neg.bus_width = wd;
/* 报告事件 */
neg.valid = CCB_TRANS_BUS_WIDTH_VALID;
xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg);
}
prepare_width_nego_rsponse(hcb, wd);
}
continue_current_hcb(softc);
return;
}
然后我们用与前面相同的笨办法处理auto-sense期间可能出现的任何
错误。否则,我们再一次进入细节。
if(hcb->flags & DOING_AUTOSENSE)
goto autosense_failed;
switch(hcb_status) {
我们考虑的下一事件是未预期的连接断开,这个事件在ABORT或
BUS DEVICE RESET消息之后被看作是正常的,其他情况下是非正常的。
case UNEXPECTED_DISCONNECT:
if(requested_abort(hcb)) {
/* 中止影响目标和LUN上的所有命令,因此将那个目标和LUN上的
* 所有断开连接的HCB也标记为中止
*/
for(h = softc->first_discon_hcb[hcb->target][hcb->lun];
h != NULL; h = hh) {
hh=h->next;
free_hcb_and_ccb_done(h, h->ccb, CAM_REQ_ABORTED);
}
ccb_status = CAM_REQ_ABORTED;
} else if(requested_bus_device_reset(hcb)) {
int lun;
/* 复位影响那个目标上的所有命令,因此将那个目标和LUN上的
* 所有断开连接的HCB标记为复位
*/
for(lun=0; lun <= OUR_MAX_SUPPORTED_LUN; lun++)
for(h = softc->first_discon_hcb[hcb->target][lun];
h != NULL; h = hh) {
hh=h->next;
free_hcb_and_ccb_done(h, h->ccb, CAM_SCSI_BUS_RESET);
}
/* 发送事件 */
xpt_async(AC_SENT_BDR, hcb->ccb->ccb_h.path_id, NULL);
/* 这是CAM_RESET_DEV请求本身,它完成了 */
ccb_status = CAM_REQ_CMP;
} else {
calculate_residue(hcb);
ccb_status = CAM_UNEXP_BUSFREE;
/* request the further code to freeze the queue */
hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN;
lun_to_freeze = hcb->lun;
}
break;
如果目标拒绝接受标签,我们就通知CAM,并返回此LUN的所有命令:
case TAGS_REJECTED:
/* 报告事件 */
neg.flags = 0 & ~CCB_TRANS_TAG_ENB;
neg.valid = CCB_TRANS_TQ_VALID;
xpt_async(AC_TRANSFER_NEG, hcb->ccb.ccb_h.path_id, &neg);
ccb_status = CAM_MSG_REJECT_REC;
/* 请求后面的代码冻结队列 */
hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN;
lun_to_freeze = hcb->lun;
break;
然后我们检查一些其他情况,处理(processing)基本上仅限于设置CCB状态:
case SELECTION_TIMEOUT:
ccb_status = CAM_SEL_TIMEOUT;
/* request the further code to freeze the queue */
hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN;
lun_to_freeze = CAM_LUN_WILDCARD;
break;
case PARITY_ERROR:
ccb_status = CAM_UNCOR_PARITY;
break;
case DATA_OVERRUN:
case ODD_WIDE_TRANSFER:
ccb_status = CAM_DATA_RUN_ERR;
break;
default:
/*以通用方法处理所有其他错误 */
ccb_status = CAM_REQ_CMP_ERR;
/* 请求后面的代码冻结队列 */
hcb->ccb->ccb_h.status |= CAM_DEV_QFRZN;
lun_to_freeze = CAM_LUN_WILDCARD;
break;
}
然后我们检查是否错误严重到需要冻结输入队列,直到它得到处理方可
解冻,如果是这样那么就这样来处理:
if(hcb->ccb->ccb_h.status & CAM_DEV_QFRZN) {
/* 冻结队列 */
xpt_freeze_devq(ccb->ccb_h.path, /*count*/1);
* /* 重新入队这个目标/LUN的所有命令,将它们返回CAM */
for(h = softc->first_queued_hcb; h != NULL; h = hh) {
hh = h->next;
if(targ == h->targ
&& (lun_to_freeze == CAM_LUN_WILDCARD || lun_to_freeze == h->lun) )
free_hcb_and_ccb_done(h, h->ccb, CAM_REQUEUE_REQ);
}
}
free_hcb_and_ccb_done(hcb, hcb->ccb, ccb_status);
schedule_next_hcb(softc);
return;
这包括通用中断处理,尽管特定处理器可能需要某些附加处理。