if (s3cmci_card_present(mmc) == 0) {
dbg(host, dbg_err, "%s: no medium present\n", __func__);
host->mrq->cmd->error = -ENOMEDIUM;
mmc_request_done(mmc, mrq);//如果卡不存在,就终止请求
} else
s3cmci_send_request(mmc);
}
接下来看s3cmci_send_request(mmc):
这个函数先判断一下请求时传输数据还是命令,如果是数据的话:
先调用s3cmci_setup_data来对S3C2410_SDIDCON寄存器进行设置,然后设置SDITIMER寄存器这就设置好了总线宽度,是否使用DMA,,并启动了数据传输模式,并且使能了下面这些中断:
imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
解析来判断是否是采用DMA进行数据传输还是采用FIFO进行数据传输
if (host->dodma)
/ because host->dodma = 0,so we don't use it
res = s3cmci_prepare_dma(host, cmd->data);//准备DMA传输,
else
res = s3cmci_prepare_pio(host, cmd->data);.//准备FIFO传输
如果是命令的话:则调用s3cmci_send_command()这个函数是命令发送的函数,和datesheet上描述的过程差不多,关于SD规范中命令的格式,请参考参考资料1.
writel(cmd->arg, host->base + S3C2410_SDICMDARG);/*先写参数寄存器
ccon = cmd->opcode & S3C2410_SDICMDCON_INDEX;//确定命令种类
ccon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART;
/*with start 2bits*/
if (cmd->flags & MMC_RSP_PRESENT)
ccon |= S3C2410_SDICMDCON_WAITRSP;
/*wait rsp*/
if (cmd->flags & MMC_RSP_136)
ccon |= S3C2410_SDICMDCON_LONGRSP;
//确定respose的种类
writel(ccon, host->base + S3C2410_SDICMDCON);
命令通道分析完了,我们分析数据通道,先分析采用FIFO方式传输是怎么样实现的。
先分析s3cmci_prepare_pio(host, cmd->data)
根据rw来判断是读还是写
if (rw) {
do_pio_write(host);
/* Determines SDI generate an interrupt if Tx FIFO fills half*/
enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
} else {
enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF
| S3C2410_SDIIMSK_RXFIFOLAST);
}
如果是写数据到SD的话,会调用do_pio_write,往FIFO中填充数据。当64字节的FIFO少于33字节时就会产生中断。如果是从SD读数据,则先使能中断,当FIFO多于31字节时时,则会调用中断服务程序,中断服务程序中将会调用do_pio_read FIFO的数据读出。
接下来分析do_pio_write:
to_ptr = host->base + host->sdidata;
fifo_free(host)用来检测fifo剩余空间
while ((fifo = fifo_free(host)) > 3) {
if (!host->pio_bytes) {
res = get_data_buffer(host, &host->pio_bytes,
/* If we have reached the end of the block, we have to
* write exactly the remaining number of bytes. If we
* in the middle of the block, we have to write full
* words, so round down to an even multiple of 4. */
if (fifo >= host->pio_bytes)//fifo的空间比pio_bytes大,表明这是读这个块的最后一次
fifo = host->pio_bytes;
/* because the volume of FIFO can contain the remaning block*/
else
fifo -= fifo & 3;/*round down to an even multiple of 4*/
host->pio_bytes -= fifo;//更新还剩余的没有写完的字
host->pio_count += fifo;/*chang the value of pio_bytes*/
fifo = (fifo + 3) >> 2;//将字节数转化为字数
/*how many words fifo contain,every time we just writ one word*/
ptr = host->pio_ptr;
while (fifo--)
writel(*ptr++, to_ptr);//写往FIFO.
host->pio_ptr = ptr;
}
注释一:注意,MMC核心为mrq->data成员分配了一个struct scatterlist的表,用来支持分散聚集,使用这种方法,这样使物理上不一致的内存页,被组装成一个连续的数组,避免了分配大的缓冲区的问题
我们看代码
if (host->pio_sgptr >= host->mrq->data->sg_len) {
dbg(host, dbg_debug, "no more buffers (%i/%i)\n",
host->pio_sgptr, host->mrq->data->sg_len);
return -EBUSY;
}
sg = &host->mrq->data->sg[host->pio_sgptr];
*bytes = sg->length;//页缓冲区中的长度
*pointer = sg_virt(sg);将页地址映射为虚拟地址
host->pio_sgptr++;这里表明我们的程序又完成了一次映射
这样,每一个mmc请求,我们只能处理scatterlist表中的一个页(块)。因此,完成一次完整的请求需要映射sg_len次
再来总结一下一个mmc写设备请求的过程:
在s3cmci_prepare_pio中我们第一次先调用do_pio_write,如果FIFO空间大于3,且能够获取到scatterlist,则我们就开始往FIFO写数据,当FIFO空间小于3,则使能TXFIFOHALF中断,在中断服务程序中,如果检测到TFDET表明又有FIFO空间了,则关闭TXFIFOHALF中断,并调用do_pio_write进行写。
数据流向如下:scatterlist-------->fifo---------->sdcard
一个mmc读设备请求的过程数据流向如下:sdcard --------> fifo ---------->scatterlist,
????关于读数据的过程,中断的触发不是很清楚,s3cmci_prepare_pio中enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF,S3C2410_SDIIMSK_RXFIFOLAST);但如果没从SD卡中读数据,怎么会引发这个中断呢?是由S3C2410_SDIIMSK_RXFIFOLAST引起的吗
接下来我们分析一下中断服务程序:
static irqreturn_t s3cmci_irq(int irq, void *dev_id)
该程序先获取所有的状态寄存器:
mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
mci_dsta = readl(host->base + S3C2410_SDIDSTA);
mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
mci_fsta = readl(host->base + S3C2410_SDIFSTA);
mci_imsk = readl(host->base + host->sdiimsk);
这些将作为中断处理的依据。
如果不是DMA模式,则处理数据的收发
if (!host->dodma) {
if ((host->pio_active == XFER_WRITE) &&
(mci_fsta & S3C2410_SDIFSTA_TFDET)) {
/*This bit indicates that FIFO data is available for transmit when
DatMode is data transmit mode. If DMA mode is enable, sd
host requests DMA operation.*/
disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
tasklet_schedule(&host->pio_tasklet);
注意我们采用tasklet这种延时机制来减少中断服务的时间,延时函数pio_tasklet中调用了do_pio_write和了do_pio_read
host->status = "pio tx";
}
if ((host->pio_active == XFER_READ) &&
(mci_fsta & S3C2410_SDIFSTA_RFDET)) {
/*
* As discussed on lkml, GENHD_FL_REMOVABLE should:
*
* - be set for removable media with permanent block devices
* - be unset for removable block devices with permanent media
*
* Since MMC block devices clearly fall under the second
* case, we do not set GENHD_FL_REMOVABLE. Userspace
* should use the block device creation/destruction hotplug
* messages to tell when the card is present.
*/这个注释如何理解呢?
}
注一:
mq->queue = blk_init_queue(mmc_request, lock);初始化将request函数与队列绑定
if (!mq->queue)
return -ENOMEM;
mq->queue->queuedata = mq;
mq->req = NULL;
blk_queue_prep_rq(mq->queue, mmc_prep_request);
//命令预处理,为驱动程序在返回evl_next_request之前,提供检查和预处理请求的机制,详细见LDD3 P485
//command prepare process
blk_queue_ordered(mq->queue, QUEUE_ORDERED_DRAIN, NULL);//
//barrier request屏障请求,防止重新组合产生的错误,设置标准后,保证请求的数据及时写入到介质。
mq->sg = kmalloc(sizeof(struct scatterlist) *
host->max_phys_segs, GFP_KERNEL);
if (!mq->sg) {
ret = -ENOMEM;
goto cleanup_queue;
}
sg_init_table(mq->sg, host->max_phys_segs);
}
//分配scatterlist结构体
mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd");最后设置了一个内核线程,线程关联的函数是mmc_queue_thread,这个很重要,我们待会分析。
接下来调用mmc_blk_set_blksize来设置block的长度为512。
一切都准备好了以后激活磁盘:add_disk(md->disk);
最后来分析request函数:
*
* Generic MMC request handler. This is called for any queue on a
* particular host. When the host is not busy, we look for a request
* on any queue on this host, and attempt to issue it. This may
* not be the queue we were asked to process.也就是说,elv_next_request返回来的
req不一定是mq->req
*/
static void mmc_request(struct request_queue *q)
{
struct mmc_queue *mq = q->queuedata;
struct request *req;
int ret;
if (!mq) {
printk(KERN_ERR "MMC: killing requests for dead queue\n");
while ((req = elv_next_request(q)) != NULL) {
do {
ret = __blk_end_request(req, -EIO,
blk_rq_cur_bytes(req));//没有可以处理的请求,则就素这个请求
} while (ret);
}
return;
}
if (!mq->req)
wake_up_process(mq->thread);//注一
}
注一:我们发现,与LDD3中介绍的块设备编程方法不同,并没有出来任何与bio结构相关的东西,当请求获取后,我们通过什么来进行数据块的传输呢,这里就是通过唤醒mq->thread线程来实现的,这个线程实际上就是mmc_queue_thread函数
static int mmc_queue_thread(void *d)
{
struct mmc_queue *mq = d;
struct request_queue *q = mq->queue;
current->flags |= PF_MEMALLOC;
down(&mq->thread_sem);
do {
struct request *req = NULL;
if (!req) {
if (kthread_should_stop()) {
set_current_state(TASK_RUNNING);
break;
}
up(&mq->thread_sem);
schedule();
down(&mq->thread_sem);
continue;
}
set_current_state(TASK_RUNNING);
//蓝色部分不是很理解,大概的意思应该还是获取一个可处理的请求
mq->issue_fn(mq, req);//注一
} while (1);
up(&mq->thread_sem);
return 0;
}
注一:我们看看issue_fn函数做了些什么,这个函数相当复杂
我们看关键的部分:
brq.data.sg = mq->sg;
brq.data.sg_len = mmc_queue_map_sg(mq);
/*
* Adjust the sg list so it is the same size as the
* request.
*/
if (brq.data.blocks != req->nr_sectors) {
int i, data_size = brq.data.blocks << 9;
struct scatterlist *sg;