EMMC读写操作的调用栈
mmc_queue_thread ->
mmc_blk_issue_rq ->
mmc_blk_issue_rw_rq ->
mmc_start_req ->
__mmc_start_data_req ->
mmc_start_request ->
omap_hsmmc_request
mmc_queue_thread
49 static int mmc_queue_thread(void *d)
50 {
51 struct mmc_queue *mq = d;
52 struct request_queue *q = mq->queue;
53
54 current->flags |= PF_MEMALLOC;
55
56 down(&mq->thread_sem);
57 do {
58 struct request *req = NULL;
59 struct mmc_queue_req *tmp;
60 unsigned int cmd_flags = 0;
61
62 spin_lock_irq(q->queue_lock);
63 set_current_state(TASK_INTERRUPTIBLE);
64 req = blk_fetch_request(q);
65 mq->mqrq_cur->req = req;
66 spin_unlock_irq(q->queue_lock);
67
68 if (req || mq->mqrq_prev->req) {
69 set_current_state(TASK_RUNNING);
70 cmd_flags = req ? req->cmd_flags : 0;
71 mq->issue_fn(mq, req);
72 if (mq->flags & MMC_QUEUE_NEW_REQUEST) {
73 mq->flags &= ~MMC_QUEUE_NEW_REQUEST;
74 continue; /* fetch again */
75 }
76
77 /*
78 * Current request becomes previous request
79 * and vice versa.
80 * In case of special requests, current request
81 * has been finished. Do not assign it to previous
82 * request.
83 */
84 if (cmd_flags & MMC_REQ_SPECIAL_MASK)
85 mq->mqrq_cur->req = NULL;
86
87 mq->mqrq_prev->brq.mrq.data = NULL;
88 mq->mqrq_prev->req = NULL;
89 tmp = mq->mqrq_prev;
90 mq->mqrq_prev = mq->mqrq_cur;
91 mq->mqrq_cur = tmp;
92 } else {
93 if (kthread_should_stop()) {
94 set_current_state(TASK_RUNNING);
95 break;
96 }
97 up(&mq->thread_sem);
98 schedule();
99 down(&mq->thread_sem);
100 }
101 } while (1);
102 up(&mq->thread_sem);
103
104 return 0;
105 }
64 blk_fetch_request 从request_queue获取一个request,设置为mmc_request的当前request
68~71 如果从blk_fetch_reuqest获得了新request,或者mmc_request的previous request正在处理当中,那么调用mq->issue_fn处理reuqest
72~75 issue_fn有可能被阻塞在mmc_wait_for_data_req_done,如果此时有新的请求到达,那么有可能会唤醒阻塞的进程 (条件是cur==null, prev!=null)。
87~90 这段代码相当奇怪,看起来把mq->mqrq_prev和mq->mqrq_cur做了置换,有点类似frame buffer ping-pong操作的意思。
mmc_blk_issue_rq
1. 调用mmc_blk_part_switch切换device的分区配置,对于正常的分区操作来说,part_config就是缺省的0值,可以参考Spec5.0中PARTITION_CONFIG (before BOOT_CONFIG) [179]
2. request->cmd_flags标识了REQ_DISCARD, REQ_FLUSH,REQ_WRITE等操作,根据request命令类型不同,分别调用:
2.1 mmc_blk_issue_secdiscard_rq 和mmc_blk_issue_discard_rq
2.2 mmc_blk_issue_flush
2.3 mmc_blk_issue_rw_rq,这个是我们要分析的读写数据流程。
mc_blk_issue_rw_rq
static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc)
如果参数req为空(无新request),或者mmc queue的previous request也为空(无未完成的request),那么mmc_blk_issue_rw_rq直接返回。
mmc_blk_prep_packed_list尝试把当前request和队列中的其他request合并,以增强性能。是否可以合并,要依赖于:
1. 控制器支持packed功能
2. device的MAX_PACKED_WRITES 大于0
3. 只对写request进行packed
正常情况下执行mmc_blk_rw_rq_prep函数,从request构造mmc_request,毕竟下发给host请求,是mmc_request,而不是block层通用的request。如果支持packed功能,那么就用pack_list来构造mmc_request
areq表示async req,实际上,只要参数@req存在,就表明这是一个新request,必然是异步传输的。
mmc_start_req 启动一个非阻塞的request,这个函数会等待前一个request完成,然后启动当前request,并立刻返回。
如果mmc_start_req返回的areq不为空,说明完成了上一次的request,
mmc_queue_bounce_post:如果是读操作,并且使用bounce buffer,那么需要把读结果从bounce buffer复制回sg buffer。所谓bounce buffer是因为某些DMA控制器只能处理连续物理内存,此时需要通过bounce buffer来达到物理内存连续性。
检查mmc_start_req返回的状态:
1. 如果是MMC_BLK_SUCCESS或者MMC_BLK_PARTIAL,需要调用blk_end_request通知block设备层,完成了本次读写request。
2. 如果是MMC_BLK_CMD_ERR,那么调用mmc_blk_reset复位host。调用mmc_blk_cmd_err尝试blk_end_request,如果发现reuqest未完成,说明本次操作失败,反之成功start_new_req
3. ....
mmc_start_req
struct mmc_async_req *mmc_start_req(struct mmc_host *host,
struct mmc_async_req *areq, int *error)
执行一个非阻塞的reuqest,如果previous request正在执行过程中,这个函数会等待previous request完成,并返回previous request;如果没有正在执行的request,则不会等待并返回NULL。
if (host->areq) {
err = mmc_wait_for_data_req_done(host, host->areq->mrq, areq);
host->areq不为空,说明有正在处理的reuqest,函数mmc_wait_for_data_req_done用来等待这个host->areq,有两个条件会唤醒该MMC上下文: is_done_rcv和is_new_req
if (!err && areq)
start_err = __mmc_start_data_req(host, areq->mrq);
上面on-going的request已经执行完毕,如果有新的request,那么调用__mmc_start_data_req执行新的request
把@areq赋值给host->areq,即当前的request变成了previous request。
__mmc_start_data_req->mmc_start_request
mmc_start_request是主流程,
mrq->done = mmc_wait_data_done;
mmc_wait_data_done会设置context_info->is_done_rcv=true,这正好是唤醒mmc_wait_for_data_req_done的条件之一。
mmc_start_reuqest实际调用host->ops->request方法,进入了平台特定的request函数,对于TI平台,改函数就是omap_hsmmc_request
omap_hsmmc_request
1958 WARN_ON(host->mrq != NULL);
1959 host->mrq = req;
1960 host->clk_rate = clk_get_rate(host->fclk);
1961 err = omap_hsmmc_prepare_data(host, req);
1962 if (err) {
1963 req->cmd->error = err;
1964 if (req->data)
1965 req->data->error = err;
1966 host->mrq = NULL;
1967 mmc_request_done(mmc, req);
1968 return;
1969 }
1970 if (req->sbc && !(host->flags & AUTO_CMD23)) {
1971 omap_hsmmc_start_command(host, req->sbc, NULL);
1972 return;
1973 }
1974
1975 omap_hsmmc_start_dma_transfer(host);
1976 omap_hsmmc_start_command(host, req->cmd, req->data);
1961 准备数据,配置并启动MMC card的DMA
1970 如果req->sbc存在,即set block count命令存在,并且host不支持AUTO CMD23,那么我们要先发送sbc
1975 启动DMA操作
1976 发送MMC command,这里才是mmc操作的终极启动命令