2014年7月13日 星期日

分析eMMC packed command in Linux kernel 之 mmc_blk_prep_packed_list

針對device是否有packed能力做一些篩選條件,若沒有的話會直接goto mmc_blk_clear_packed離開此函式。
分析一、
max_blk_count = min(card->host->max_blk_count,
                        card->host->max_req_size >> 9);
        if (unlikely(max_blk_count > 0xffff))
                max_blk_count = 0xffff;
也就是不管如何host端最多最多一次送的blk_count就是65535blocks.

分析二、
max_phys_segs = queue_max_segments(q);

        req_sectors += blk_rq_sectors(cur);
        phys_segments += cur->nr_phys_segments;

        if (rq_data_dir(cur) == WRITE) {
                req_sectors++;
                phys_segments++;
        }
藉由queue_max_segments得到request_queuemax_segmants值。
藉由blk_rq_sectors得到目前request要傳送sector(512bytes)數目。
/* Number of scatter-gather DMA addr+len pairs after
* physical address coalescing is performed.
*/
unsigned short nr_phys_segments;
藉由nr_phys_segments的註解說明,我們知道紀錄的是scatter-gather DMA addr+len pairs在做完physical address coalescing的數量。
至於physical address coalescing是怎麼樣的行為目前先不探討。
接著如果當前的request是一個寫入要求,req_sectors以及phys_segments各加一。WHY? (直覺想到的是要傳packed commandheader,但是若為packed read也是需要header,目前暫留此懸疑。)
再來進入此函數的重頭戲,
while (reqs < max_packed_rw - 1) {
                spin_lock_irq(q->queue_lock);
                next = blk_fetch_request(q);
                spin_unlock_irq(q->queue_lock);
                if (!next)
                        break;
先利用spin_lock機制鎖住此requestrequest_queue。確保在做blk_fecth_request時的同步問題。
/**
 * blk_fetch_request - fetch a request from a request queue
 * @q: request queue to fetch a request from
 *
 * Description:
 *     Return the request at the top of @q.  The request is started on
 *     return and LLD can start processing it immediately.
 *
 * Return:
 *     Pointer to the request at the top of @q if available.  Null
 *     otherwise.
 *
 * Context:
 *     queue_lock must be held.
 */
struct request *blk_fetch_request(struct request_queue *q)
{
        struct request *rq;

        rq = blk_peek_request(q);
        if (rq)
                blk_start_request(rq);
        return rq;
}
EXPORT_SYMBOL(blk_fetch_request);
分析三、
註解說明其實已經很清楚說明此函數(blk_fetch_request)的任務,這邊我還不了解的是LLD是甚麼? Wiki: LLD(Lower level design, a stage in project management of SW development or other computer-related projects, following the High-level design) 在此LLD是指將此request交給dirver去處理。
分析三-甲、
/**
 * blk_peek_request - peek at the top of a request queue
 * @q: request queue to peek at
 *
 * Description:
 *     Return the request at the top of @q.  The returned request
 *     should be started using blk_start_request() before LLD starts
 *     processing it.
 *
 * Return:
 *     Pointer to the request at the top of @q if available.  Null
 *     otherwise.
 *
 * Context:
 *     queue_lock must be held.
 */
struct request *blk_peek_request(struct request_queue *q)
再度藉由清楚明瞭的註解知道此函數的目的。(良好的註解習慣是優秀程式設計師必備) 另外會再寫一篇詳細分析blk_peek_request跟io-scheduler相關的探討
分析三-乙、
/**
 * blk_start_request - start request processing on the driver
 * @req: request to dequeue
 *
 * Description:
 *     Dequeue @req and start timeout timer on it.  This hands off the
 *     request to the driver.
 *
 *     Block internal functions which don't want to start timer should
 *     call blk_dequeue_request().
 *
 * Context:
 *     queue_lock must be held.
 */
void blk_start_request(struct request *req)
{
        blk_dequeue_request(req);

        /*
         * We are now handing the request to the hardware, initialize
         * resid_len to full count and add the timeout handler.
         */
        req->resid_len = blk_rq_bytes(req);
        if (unlikely(blk_bidi_rq(req)))
                req->next_rq->resid_len = blk_rq_bytes(req->next_rq);

        blk_add_timer(req);
}
EXPORT_SYMBOL(blk_start_request);
在註解中提到若是不想要啟動timeout timer,應該直接呼叫blk_dequeue_request呼叫blk_dequeue_request後,取得目前request要傳送/接收的data長度,並且藉由blk_bidi_rq知道此request後是否接著還有request,接著取得下個request要傳送/接收的data長度,我想這邊是為了提升request收送的效率。
/**
 * blk_add_timer - Start timeout timer for a single request
 * @req: request that is about to start running.
 *
 * Notes:
 *    Each request has its own timer, and as it is added to the queue, we
 *    set up the timer. When the request completes, we cancel the timer.
 */
void blk_add_timer(struct request *req)
這邊就不再多做說明blk_add_timer了。
接著有幾個條件會讓程序走向blk_reqeue_request:
1.      下個requestREQ_DISCARD 或是 REQ_FLUSH
2.      下個requestIO方向不同
3.      下個requestreliable write但是device不支援
4.      加上下個request要求的data block count後,大於host最大的block count
5.      加上下個requestnr_phys_segments後,大於host max_phys_segs
再來,我們看看blk_reqeue_request在做甚麼事。
/**
 * blk_requeue_request - put a request back on queue
 * @q:             request queue where request should be inserted
 * @rq:           request to be inserted
 *
 * Description:
 *    Drivers often keep queueing requests until the hardware cannot accept
 *    more, when that condition happens we need to put the request back
 *    on the queue. Must be called with queue lock held.
 */
void blk_requeue_request(struct request_queue *q, struct request *rq)
因此當hardware還能負荷request或是沒有觸動到上面提到的幾個條件,就會持續呼叫list_add_tailentry加到packed_list中。
/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
        __list_add(new, head->prev, head);
}
離開重頭戲while迴圈後,得知reqs需要執行之數量,呼叫list_add並設定相關packed_num, packed_retries參數後結束整個函式。
/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
        __list_add(new, head, head->next);
}
來看一下,__list_add吧。
static inline void __list_add(struct list_head *new,
                              struct list_head *prev, struct list_head *next)
{
        next->prev = new;   //1
        new->next = next;   //2
        new->prev = prev;   //3
        prev->next = new;   //4
}



1 則留言:

  1. WHY? (直覺想到的是要傳packed command的header,但是若為packed read也是需要header,目前暫留此懸疑。)
    emmc spec中对于packed read的定义说:不需要额外加上一个block,第一次发cmd23 count block = one
    第二次发cmd23 count block = all block counts of the individual reads

    回覆刪除