SD卡分析一

本文先介紹了一下MMC 的基本框架結構,然後採用自底向上的方法來分析整個MMC 層是如何共同作用的。閱讀時請結合參考資料1和2.
參考資料:
1.SD Memory Card Specifications / Part 1. Physical Layer Specification; Version 1.0 
2.LDD3 CHAPTER-16 BLOCK DEVICE
3. http://www.sdcard.org

1.硬件基礎:
http://blog.ednchina.com/yelov/198217/message.aspx
2.MMC 子系統的基本框架結構:
很遺憾,內核沒有爲我們提供關於MMC 子系統的文檔,在谷歌上搜索了很多,也沒有找到相關文章。只能自己看代碼分析了,可能有很多理解不對的地方,希望研究過這方面的朋友多郵件交流一下。
MMC 子系統的代碼在kernel/driver/MMC下,目前的MMC 子系統支持一些形式的記憶卡:SD,SDIO,MMC.由於筆者對SDIO的規範不是很清楚,後面的分析中不會涉及。MMC 子系統範圍三個部分:
HOST部分是針對不同主機的驅動程序,這一部是驅動程序工程師需要根據自己的特點平臺來完成的。
CORE部分:這是整個MMC 的核心存,這部分完成了不同協議和規範的實現,併爲HOST層的驅動提供了接口函數。
CARD部分:因爲這些記憶卡都是塊設備,當然需要提供塊設備的驅動程序,這部分就是實現了將你的SD卡如何實現爲塊設備的。
3.HOST層分析:
HOST層實現的就是我們針對特定主機的驅動程序,這裏以mini2440的s3cmci.c爲例子進行分析,我們先採用 platform_driver_register(&s3cmci_2440_driver)註冊了一個平臺設備,接下來重點關注probe函數。在這個函數總,我們與CORE的聯繫是通過下面三句實現的。首先分配一個mmc_host結構體,注意sizeof(struct s3cmci_host),這樣就能在mmc_host中找到了s3cmci_host,嵌入結構和被嵌入的結構體能夠找到對方在Linux內核代碼中的常用技術了。接下來爲mmc->pos賦值,s3cmci_ops結構實現了幾個很重要的函數,待會我一一介紹。中間還對mmc 結構的很多成員進行了賦值,最後將mmc 結構加入到MMC 子系統,mmc_alloc_host,以及mmc_add_host 的具體做了什麼事情,我們在下節再分析,這三句是些MMC 層驅動必須包含的。
mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
mmc->ops     = &s3cmci_ops;
……………
s3cmci_ops 中包含了四個函數:
static struct mmc_host_ops s3cmci_ops = {
    .request    = s3cmci_request,
    .set_ios    = s3cmci_set_ios,
    .get_ro        = s3cmci_get_ro,
    .get_cd        = s3cmci_card_present,
};
我們從簡單的開始分析,這些函數都會在core部分被調用:
s3cmci_get_ro:這個函數通過從GPIO讀取,來判斷我們的卡是否是寫保護的
s3cmci_card_present:這個函數通過從GPIO讀取來判斷卡是否存在
s3cmci_set_ios:s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
依據核心層傳遞過來的ios,來設置硬件IO,包括引腳配置,使能時鐘,和配置總線帶寬。
s3cmci_request:這個函數是最主要,也最複雜的函數,實現了命令和數據的發送和接收,
當CORE部分需要發送命令或者傳輸數據時,都會調用這個函數,並傳遞mrq 請求。
static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
    struct s3cmci_host *host = mmc_priv(mmc );
    host->status = "mmc request";
    host->cmd_is_stop = 0;
    host->mrq = mrq;

    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)) {

            disable_imask(host,
                      S3C2410_SDIIMSK_RXFIFOHALF |
                      S3C2410_SDIIMSK_RXFIFOLAST);

            tasklet_schedule(&host->pio_tasklet);
            host->status = "pio rx";
        }
    接下來的很多代碼是對其他的一些類型中斷的處理。
最後來分析DMA模式:這種模式下不需要CPU的干預。S3C2440的DMA有4個通道,我們選擇了通道0
static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
{
    int dma_len, i;
    int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;

    BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR);

    s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW);//注一
    s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);

    dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
                 (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE);//注二

    if (dma_len == 0)
        return -ENOMEM;

    host->dma_complete = 0;
    host->dmatogo = dma_len;

    for (i = 0; i < dma_len; i++) {
        int res;

        dbg(host, dbg_dma, "enqueue %i:%u@%u/n", i,
            sg_dma_address(&data->sg),
            sg_dma_len(&data->sg));

        res = s3c2410_dma_enqueue(host->dma, (void *) host,
                      sg_dma_address(&data->sg),
                      sg_dma_len(&data->sg));

        if (res) {
            s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
            return -EBUSY;
        }
    }

    s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);

    return 0;
}
注一:這個函數先調用 s3c2410_dma_devconfig來配置DMA 源/目的的意見類型和地址,注意我們這裏的設備地址host->mem->start + host->sdidata實際上就是SDIDATA寄存器的地址值,如果是寫SD卡,則爲目的地址,否則爲源地址。然後調用 s3c2410_dma_set_buffdone_fn(host->dma, s3cmci_dma_done_callback);
設置dma通道0的回調函數。
注二:
dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
                 (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
這裏進行分散/聚集映射(P444,LDD3),返回值是傳送的DMA緩衝區數,可能會小於sg_len,也就是說sg_len與dma_len可能是不同的。
sg_dma_address(&data->sg),返回的是總線(DMA)地址
sg_dma_len(&data->sg)); 返回的是緩衝區的長度。
最後調用 s3c2410_dma_enqueue(host->dma, (void *) host,
                      sg_dma_address(&data->sg),
                      sg_dma_len(&data->sg));
對每個DMA緩衝區進行排隊,等待處理。
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);啓動DMA
這樣DMA緩衝區就和scatterlist聯繫起來,當寫數據時,scatterlist中的數據由於上面的映射關係會直接“拷貝”到DMA緩衝區,當讀數據時則反之。整個過程不需要CPU干預,自動完成。

發佈了1 篇原創文章 · 獲贊 0 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章