scsi磁盘驱动中将request请求转化为scsi命令的过程(sd_prep_fn)

scsi磁盘驱动中将request请求转化为scsi命令的过程

该过程主要发生在sd_prep_fn函数中,下面分析该函数


static int sd_prep_fn(struct request_queue *q, struct request *rq)
{
    struct scsi_cmnd *SCpnt;
    struct scsi_device *sdp = q->queuedata;
    struct gendisk *disk = rq->rq_disk;
    struct scsi_disk *sdkp;
    sector_t block = blk_rq_pos(rq);//request请求的起始扇区位置
    sector_t threshold;
    unsigned int this_count = blk_rq_sectors(rq);//request请求所需要传输多少个扇区大小
    int ret, host_dif;
    unsigned char protect;
    ...
    ret = scsi_setup_fs_cmnd(sdp, rq);
/*该函数比较重要,会在后面单独分析,主要功能是为request的每一个bio创建DMA映射*/
    if (ret != BLKPREP_OK)
        goto out;
    SCpnt = rq->special;//scsi_setup_fs_cmnd已经完成SCpnt的分配,并完成部分初始化,且将申请的scsi_cmnd赋给rq->special
    sdkp = scsi_disk(disk);

    /* from here on until we're complete, any goto out
     * is used for a killable error condition */
    ret = BLKPREP_KILL;
    ...

/*根据扇区大小对内部变量block和this_count进行调整,其中block表示将要对磁盘读写的起始扇区号,this_count表示将要读入scsi_cmnd对应的那个缓冲区的字节数。这个缓冲区是通过前面scsi_init_io函数调用scsi_alloc_sgtable获得的,后面会在scsi_setup_fs_cmnd对其进行分析*/

//因为我们在request请求中假设扇区大小是512,因此得知设备真正扇区大小后需对起始扇区号和扇区数做出相应调整
    if (sdp->sector_size == 1024) {
        if ((block & 1) || (blk_rq_sectors(rq) & 1)) {
            scmd_printk(KERN_ERR, SCpnt,
                    "Bad block number requested\n");
            goto out;
        } else {
            block = block >> 1;
            this_count = this_count >> 1;
        }
    }
    if (sdp->sector_size == 2048) {
        if ((block & 3) || (blk_rq_sectors(rq) & 3)) {
            scmd_printk(KERN_ERR, SCpnt,
                    "Bad block number requested\n");
            goto out;
        } else {
            block = block >> 2;
            this_count = this_count >> 2;
        }
    }
    if (sdp->sector_size == 4096) {
        if ((block & 7) || (blk_rq_sectors(rq) & 7)) {
            scmd_printk(KERN_ERR, SCpnt,
                    "Bad block number requested\n");
            goto out;
        } else {
            block = block >> 3;
            this_count = this_count >> 3;
        }
    }

    //通过rq_data_dir宏获得request的传输方向,如果是WRITE就把scsi命令的操作码设置成WRITE_6,否则设置成READ_6。
    if (rq_data_dir(rq) == WRITE) {
        if (!sdp->writeable) {
            goto out;
        }
        SCpnt->cmnd[0] = WRITE_6;
        SCpnt->sc_data_direction = DMA_TO_DEVICE;

        if (blk_integrity_rq(rq))
            sd_dif_prepare(rq, block, sdp->sector_size);

    } else if (rq_data_dir(rq) == READ) {
        SCpnt->cmnd[0] = READ_6;
        SCpnt->sc_data_direction = DMA_FROM_DEVICE;
    } else {
        scmd_printk(KERN_ERR, SCpnt, "Unknown command %llx\n", (unsigned long long) rq->cmd_flags);
        goto out;
    }
    ...
    /* Set RDPROTECT/WRPROTECT if disk is formatted with DIF */
    host_dif = scsi_host_dif_capable(sdp->host, sdkp->protection_type);
    if (host_dif)
        protect = 1 << 5;
    else
        protect = 0;

    if (host_dif == SD_DIF_TYPE2_PROTECTION) {//一般不会用DIF给磁盘设置RDPROTECT/WRPROTECT,不用管这部分逻辑
        ...
    } else if ((this_count > 0xff) || (block > 0x1fffff) ||
           scsi_device_protection(SCpnt->device) ||
           SCpnt->device->use_10_for_rw) {
    //通常都会进入该分支
        if (this_count > 0xffff)
            this_count = 0xffff;
        //各个位置存放的是什么详情请看scsi sdb命令格式
        SCpnt->cmnd[0] += READ_10 - READ_6;
        SCpnt->cmnd[1] = protect | ((rq->cmd_flags & REQ_FUA) ? 0x8 : 0);
        SCpnt->cmnd[2] = (unsigned char) (block >> 24) & 0xff;
        SCpnt->cmnd[3] = (unsigned char) (block >> 16) & 0xff;
        SCpnt->cmnd[4] = (unsigned char) (block >> 8) & 0xff;
        SCpnt->cmnd[5] = (unsigned char) block & 0xff;//2到5存磁盘起始扇区号
        SCpnt->cmnd[6] = SCpnt->cmnd[9] = 0;
        SCpnt->cmnd[7] = (unsigned char) (this_count >> 8) & 0xff;
        SCpnt->cmnd[8] = (unsigned char) this_count & 0xff;//7到8存传输扇区数
    } else {
        if (unlikely(rq->cmd_flags & REQ_FUA)) {
            /*
             * This happens only if this drive failed
             * 10byte rw command with ILLEGAL_REQUEST
             * during operation and thus turned off
             * use_10_for_rw.
             */
            scmd_printk(KERN_ERR, SCpnt,
                    "FUA write on READ/WRITE(6) drive\n");
            goto out;
        }

        SCpnt->cmnd[1] |= (unsigned char) ((block >> 16) & 0x1f);
        SCpnt->cmnd[2] = (unsigned char) ((block >> 8) & 0xff);
        SCpnt->cmnd[3] = (unsigned char) block & 0xff;//上述三个字节用于存起始扇区号
        SCpnt->cmnd[4] = (unsigned char) this_count;//该字节存放传输扇区数目
        SCpnt->cmnd[5] = 0;
    }
    SCpnt->sdb.length = this_count * sdp->sector_size;

    /* If DIF or DIX is enabled, tell HBA how to handle request */
    if (host_dif || scsi_prot_sg_count(SCpnt))
        sd_prot_op(SCpnt, host_dif);


    SCpnt->transfersize = sdp->sector_size;//该命令要传输多少个扇区大小
    SCpnt->underflow = this_count << 9;
    SCpnt->allowed = SD_MAX_RETRIES;

    /*
     * This indicates that the command is ready from our end to be
     * queued.
     */
    ret = BLKPREP_OK;
 out:
    return scsi_prep_return(q, rq, ret);
    //正常的话,scsi_prep_return函数返回BLKPREP_OK。prep表示prepare的意思,BLKPREP_OK就说明准备好了,或者说准备就绪。
}

下面我们来分析一下scsi_setup_fs_cmnd的代码,该函数主要根据request请求创建SCSI CDB以及为request的每一个bio创建DMA映射。

int scsi_setup_fs_cmnd(struct scsi_device *sdev, struct request *req)
{
    struct scsi_cmnd *cmd;
    ...
    cmd = scsi_get_cmd_from_req(sdev, req);//获取cmd
    if (unlikely(!cmd))
        return BLKPREP_DEFER;

    memset(cmd->cmnd, 0, BLK_MAX_CDB);
    return scsi_init_io(cmd, GFP_ATOMIC);//对cmd进行初始化
}
static struct scsi_cmnd *scsi_get_cmd_from_req(struct scsi_device *sdev,
        struct request *req)
{
    struct scsi_cmnd *cmd;

    //将cmd与req->special关联起来,后面会用到这个元素
    if (!req->special) {
        cmd = scsi_get_command(sdev, GFP_ATOMIC);
        if (unlikely(!cmd))
            return NULL;
        req->special = cmd;
    } else {
        cmd = req->special;
    }

    cmd->tag = req->tag;
    cmd->request = req;

    cmd->cmnd = req->cmd;//cmd中存放命令的缓冲区其实是request中的cmd对应的缓冲区
    cmd->prot_op = SCSI_PROT_NORMAL;

    return cmd;
}
/*
 * Returns:     0 on success
 *      BLKPREP_DEFER if the failure is retryable
 *      BLKPREP_KILL if the failure is fatal
 */
int scsi_init_io(struct scsi_cmnd *cmd, gfp_t gfp_mask)
{
    struct request *rq = cmd->request;

    int error = scsi_init_sgtable(rq, &cmd->sdb, gfp_mask);
    ...
}

为request的每一个bio创建DMA映射的过程实际是由scsi_init_sgtable完成,由于篇幅原因我们将在下篇分析。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值