虚位以待(AD)
虚位以待(AD)
首页 > 操作系统 > LINUX > scsi磁盘驱动中将request请求转化为scsi命令的过程

scsi磁盘驱动中将request请求转化为scsi命令的过程
类别:LINUX   作者:码皇   来源:<a href="http://blog.csdn.net/weixin_36145588" target="_blank" rel="nofo   点击:

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 requestedn");
    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 requestedn");
    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 requestedn");
    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 %llxn", (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;
    SCpnt->cmnd[6] = SCpnt->cmnd[9] = 0;
    SCpnt->cmnd[7] = (unsigned char) (this_count >> 8) & 0xff;
    SCpnt->cmnd[8] = (unsigned char) this_count & 0xff;
    }
    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) driven");
    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完成,由于篇幅原因我们将在下篇分析。

相关热词搜索: