2011年7月28日 星期四

SD/MMC驅動程式面面談

前言
MMC/SD在現在崁入式系統常被使用,因為它的體積小而且目前最新的版本可以支援到2TB,所以現在的崁入式的系統中自然而然會選用它來做儲存媒介。而SD/MMC其標準定義了實體層的硬體介面訊號,以及第二層為指令及資料讀寫的定義。它可分為SD/MMC為第一版只支援至2G,而SDHC則是第二版,可以支援至32G,它和第一版的差異,除了實體層的工作頻率增加,可以較快速的讀取之,在第二層也有擴充其功能,使其可以存取更大的檔案系統,而目前最新的為SDXC它可以支援至2TB,它又將工件往上提升,可是可以向下相容,也就是舊規格的SD卡也可以用,現在我們來介紹如何在driver中撰這些程式碼。
MMC/SD程式目錄
MMC/SD在Linux Kernel原始程式碼中的擺放位置為drivers/mmc,在其中又分為三個子目錄,分別為host、core及card,而這三個目錄其所包含內容如下:
host 
則是各家SD controller晶片廠所實現的部分,其主要是控制硬體,並將上層的所下的指令給轉換至硬體層,我們所要撰寫的驅動程式也是在這個目錄之下。
core
這是SD/MMC的管理及核心程式的地方,其中包含了第二層大家共通的部分,也是提供實體註冊及管理的部分,這個部分是大家一起共用,所以不用修改。
card
這是kernel中的檔案系統和管理中心溝通部分,也就是將檔案系統的需求轉換至核心管理程式,而核心管理再將其轉換至實體層,這個部分也是大家一起共用,所以也不需要修改。
在2.6.18之後有支援SDHC其主要是第二層的指令增加,所以舊的SD硬體 (SD host) 可以經軟體的改變而可以支援SDHC,只是因為硬體沒有改變,因此無法支援較高速的SD card。而在載入驅動程式需如下:
insmod mmc_core.ko
insmod mmc_block.ko
insmod your_hardware_SD_driver.ko
因為這是一個底層的驅動程式,所以你可以在上層載入任何的檔案系統,因為檔案系統是一個純軟體的驅動程式,所以和硬體無關,只是FAT32最大所能支援的檔案系統就只有32G,所以若要使用較大的檔案系統則必需使用其它的檔案系統。
系統驅動程式結構
我們先來了解一下linux kernel其中驅動程式的結構,在linux kernel的驅動程式中會將各種元件給予分門別類,並且在各種類別中會給予建構一個管理層的驅動程式,其主要目的是要建立和應用程式之間的標準介面,並且管理相同類別中的各家不同的硬體廠商,如此一來即可規範硬體廠商如何撰寫廠商,避免因不同的廠商而產生不同的驅動程式的撰寫格式不同,或者造成應用程式的介面,進而造成使用者因不同的廠商而要修改軟體。其架構圖如下:
              圖一:驅動程式系統架構
圖一清楚地表示前面所說明的各個目錄所負責的事情,而我們今天所要實現就是在host的目錄之下,向core註冊一個硬體,如此一來就可很輕易地連接到整個檔案系統,而檔案系統維護的部分,則是由上層的檔案所維護,對硬體層而言則是執行上層的指令,並且是完全是被動的,不會主動自行執行任何指令。
MMC core所提供的API
在介紹如何撰寫前,先來了解有那些MMC core API可供使用,其所用的include file是,而API如下:
struct mmc_host *mmc_alloc_host(int extra, struct device *)
這是allocate MMC註冊時所需的資料結構記憶體,另外可以多要一些記憶體來存放驅動程式的私有資料,其input/output說明如下:
Input :
int extra – 需要額外記憶體的大小,其存放位置的指標將會放在return回來的mmc資料結構中的private指標,它將會是緊接在mmc資料結構的最後面,或者經由呼叫mmc_priv(struct mmc_host *)來取得其指標。
struct device * – 為驅動程式進入時系統會傳過來的一個參數,只要將其再轉傳過去即可。
Output :
其返回值為一個mmc_host資料結構的位址point,這個資料結構會在後面介紹,這個point位址將會給予保留,因為我們需要在其中幾個call back欄位填入我們要實現的功能,也就是提供給上層驅動程式如何溝通的介面。
void *mmc_priv(struct mmc_host *host)
這是取回私有資料的指標,通常會再給予強迫轉型給local變數。
Input :
struct mmc_host *host – 是mmc_alloc_host API所return回來的class pointer。
Output :
其返回值為一個指標位址。
int mmc_add_host(struct mmc_host *)
 這是增加一個SD host controller,而其所需要的資料結構則是由上一個API所取得回來的,而登錄加入時上層的管理者會掃瞄一次是否有SD卡已經插上來,若有則增加一個SD disk,若没有也會設定該SD host所支援的電壓及頻率。
Iuput:
struct mmc_host * - 是由mmc_alloc_host API所return回來的class pointer。
Output:
0代表成功,非0值代表錯誤。
void mmc_remove_host(struct mmc_host *)
這是移除一個SD host controller,其做的事情和mmc_add_host()是相反的事。
Input:
struct mmc_host * - 是由mmc_alloc_host API所return回來的class pointer。
Output:
void mmc_free_host(struct mmc_host *)
這是將mmc_alloc_host()所取得的資料結構記憶體空間歸還給系統,另外也會將多取得的私有空間也歸還給系統,所以在這之後將不可再使用該資料結構的記憶體空間。
Input:
struct mmc_host * - 是由mmc_alloc_host API所return回來的class pointer。
Output:
void mmc_detect_change(struct mmc_host *, unsigned long delay)
在控制器偵測到有SD卡的插入或拔出都會呼叫這個API通知上層管理層加入一張SD卡或者移除一張SD卡,請不要搞亂並沒有移除或增加一個控制器,只是SD卡而己,但在移除時未完成的SD動作,都需要給予放棄。
Input:
struct mmc_host * - 是由mmc_alloc_host API所return回來的class pointer。
unsigned long delay – 當是插入時通常會設定一個值,以等待插入的SD卡電氣訊號穩定,而移除時通常其值會是0。
Output:
void mmc_request_done(struct mmc_host *, struct mmc_request *)
這是一個非常也常用到的API,原則上層管理層下達指令給控制器時都是透過mmc_request,而當控制器完成該指令時則需要呼叫這個API來通知上層已完成該指令,而在SD的控制指令則是半雙工的,一個指令完成之後才會有下一個,不會有連續好幾個指令產生。另外指令是包含讀寫資料。
Input:
struct mmc_host * - 是由mmc_alloc_host API所return回來的class pointer。
struct mmc_request * - 是由上層所下的指令資料結構。
Output:
資料結構
如前面所提,其中最重要的資料結構宣告如下:
struct mmc_ios {
        unsigned int    clock;                  /* clock rate */
        unsigned short  vdd;
        unsigned char   bus_mode;    /* command output mode */
        unsigned char   chip_select;            /* SPI chip select */
        unsigned char   power_mode;   /* power supply mode */
        unsigned char   bus_width;        /* data bus width */
        unsigned char   timing;      /* timing specification used */
};
這是一個SD卡的基本設定值,其中要支援的電壓值、使用的頻率等等,其使用時機當有SD卡插上會要求設定這些以啓動插入的SD卡。
struct mmc_host_ops {
        void (*request)(struct mmc_host *host, struct mmc_request *req);
        void    (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
        int     (*get_ro)(struct mmc_host *host);
        int     (*get_cd)(struct mmc_host *host);
        void    (*enable_sdio_irq)(struct mmc_host *host, int enable);
};
這是設定讓SD host所能提供功能,而其中的request最為重要,它是SD要對SD卡任何的動作需求,包含讀寫資料,以其它SD控制指令下達,set_ios則是設定以上資料結構的call back function,get_ro則是回覆這個SD卡是否可以寫入。
struct mmc_host {
        struct device           *parent;
        struct device           class_dev;
        int                     index;
        const struct mmc_host_ops *ops;
        unsigned int            f_min;
        unsigned int            f_max;
        u32                     ocr_avail;
        unsigned long           caps;           /* Host capabilities */
        /* host specific block data */
unsigned int max_seg_size;   /* see blk_queue_max_segment_size */
unsigned short max_hw_segs;/* see blk_queue_max_hw_segments */
unsigned short max_phys_segs; /* see blk_queue_max_phys_segments */
        unsigned short          unused;
   unsigned int max_req_size; /* maximum number of bytes in one req */
        unsigned int max_blk_size; /* maximum size of one mmc block */
 unsigned int max_blk_count; /* maximum number of blocks in one req */
        /* private data */
        spinlock_t              lock;    /* lock for claim and bus ops */
        struct mmc_ios          ios;     /* current io bus settings */
        u32                     ocr;     /* the current OCR setting */
        /* group bitfields together to minimize padding */
        unsigned int            use_spi_crc:1;
        unsigned int    claimed:1;      /* host exclusively claimed */
        unsigned int   bus_dead:1;     /* bus has been released */
#ifdef CONFIG_MMC_DEBUG
        unsigned int     removed:1;   /* host is being removed */
#endif
        struct mmc_card    *card; /* device attached to this host */
        wait_queue_head_t       wq;
        struct delayed_work     detect;
        const struct mmc_bus_ops *bus_ops;   /* current bus driver */
        unsigned int            bus_refs;       /* reference counter */
        unsigned int            sdio_irqs;
        struct task_struct      *sdio_irq_thread;
        atomic_t                sdio_irq_thread_abort;
#ifdef CONFIG_LEDS_TRIGGERS
        struct led_trigger      *led;           /* activity led */
#endif
        struct dentry           *debugfs_root;
#ifdef CONFIG_MMC_EMBEDDED_SDIO
        struct {
                struct sdio_cis                 *cis;
                struct sdio_cccr                *cccr;
                struct sdio_embedded_func    *funcs;
                int                             num_funcs;
        } embedded_sdio_data;
#endif
        unsigned long           private[0] ____cacheline_aligned;
};
這是記錄SD host的一些資訊,SD中所有驅動程式將會隨時使用這個資訊。
struct mmc_command {
        u32                     opcode;
        u32                     arg;
        u32                     resp[4];
        unsigned int            flags;    /* expected response type */
        unsigned int            retries;     /* max number of retries */
        unsigned int            error;          /* command error */
/*
 * Standard errno values are used for errors, but some have specific
 * meaning in the MMC layer:
 *
 * ETIMEDOUT    Card took too long to respond
 * EILSEQ       Basic format problem with the received or sent data
 *              (e.g. CRC check failed, incorrect opcode in response
 *              or bad end bit)
 * EINVAL       Request cannot be performed because of restrictions
 *              in hardware and/or the driver
 * ENOMEDIUM    Host can determine that the slot is empty and is
 *              actively failing requests
 */
        struct mmc_data  *data; /* data segment associated with cmd */
        struct mmc_request   *mrq;       /* associated request */
};
這個資料結構只有在下指令時會用到,但是有一個很重要的概念是,讀寫資料也是指令的一種,所以意思就是SD的管理層軟體全部使用command和實體的驅動程式做溝通。
struct mmc_data {
        unsigned int  timeout_ns;  /* data timeout (in ns, max 80ms) */
        unsigned int     timeout_clks;   /* data timeout (in clocks) */
        unsigned int            blksz;          /* data block size */
        unsigned int            blocks;         /* number of blocks */
        unsigned int            error;          /* data error */
        unsigned int            flags;
        unsigned int            bytes_xfered;
        struct mmc_command      *stop;     /* stop command */
        struct mmc_request      *mrq;       /* associated request */
        unsigned int            sg_len;       /* size of scatter list */
        struct scatterlist      *sg;            /* I/O scatter list */
};
這是當指令為讀寫資料時會用到的資料結構,其中有包含要存放的buffer,但有點很重要,就是這個buffer是使用page的管理,而不是raw buffer的方式。
struct mmc_request {
        struct mmc_command      *cmd;
        struct mmc_data         *data;
        struct mmc_command      *stop;
        void                    *done_data;     /* completion data */
        void  (*done)(struct mmc_request *);/* completion function */
};
這是來存放指令的資料結構。
範例程式
static void vicsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct vicsd_host *host=mmc_priv(mmc);
struct mmc_command *cmd;
spin_lock(&host->lock);
host->mrq = mrq;
cmd = mrq->cmd;
// if no card inserted, return timeout error
if ( readl(&host->reg->status) & MSD_CARD_DETECT ) { // card is removed
cmd->error = MMC_ERR_TIMEOUT;
goto request_done;
}
// request include data or not
if ( cmd->data ) {
vicsd_prepare_data(host, cmd->data);
}
// do request command
vicsd_send_command(host, cmd);
if ( cmd->data && cmd->error == MMC_ERR_NONE ) {
spin_unlock(&host->lock);
return;
}
request_done:
vicsd_request_done(host);
spin_unlock(&host->lock);
}
static void vicsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct vicsd_host *host=mmc_priv(mmc);
spin_lock(&host->lock);
if (ios->clock) {
// set SD host controller frequency
} else if ( !(readl(&host->reg->clock_control) & MSD_CLK_DIS) ) {
/*
* Ensure that the clock is off.
*/
}
if ( ios->power_mode == MMC_POWER_OFF ) {
// power off host controller
} else {
// power on host controller
}
#if 1
// set bus width
if ( ios->bus_width == MMC_BUS_WIDTH_1 ) {
………………………….
} else {
…………………………..
}
#endif
spin_unlock(&host->lock);
}
static int vicsd_get_ro(struct mmc_host *mmc)
{
struct vicsd_host *host=mmc_priv(mmc);
if ( readl(&host->reg->status) & MSD_WRITE_PROT )
return 1;
else
return 0;
}
static struct mmc_host_ops vicsd_ops = {
.request = vicsd_request,
.set_ios = vicsd_set_ios,
.get_ro = vicsd_get_ro,
};
static int vicsd_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mmc_host *mmc;
int ret;
// allocate a MMC host data structure
mmc = mmc_alloc_host(sizeof(struct vicsd_host), dev);
if (!mmc) {
ret = -ENOMEM;
goto out;
}
// initialize the MMC host data structure
mmc->ops = &vicsd_ops;
mmc->f_min = 400000;
mmc->f_max = 25000000;
mmc->mode = MMC_MODE_SD;
#if 1
mmc->ocr_avail = 0xffff00; // support 2.0v - 3.6v power
#else
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
mmc->caps = MMC_CAP_4_BIT_DATA;
mmc->max_hw_segs = 128;
mmc->max_phys_segs = 128;
mmc->max_sectors = 128;
mmc->max_seg_size = mmc->max_sectors * 512;
#endif
host = mmc_priv(mmc);
host->mmc = mmc;
…………………………………………………..
/*
* Ensure that the host controller is shut down, and setup
* with our defaults.
*/
// reset the SD card controller
………………………………………………
// to check any card inserted or not
……………………………………………
// initialize the SD card host register
…………………………………………..
dev_set_drvdata(dev, mmc);
mmc_add_host(mmc);
return 0;
 out:
if (mmc)
mmc_free_host(mmc);
return ret;
}
static struct platform_driver vicsd_driver = {
.probe          = vicsd_probe,
.remove         = vicsd_remove,
.suspend = vicsd_suspend,
.resume = vicsd_resume,
.driver = {
.name = "victor-sd",
},
};
static int __init vicsd_init(void)
{
int ret;
ret=platform_driver_register(&vicsd_driver);
if ( ret ) {
printk("fail !\n");
platform_driver_unregister(&vicsd_driver);
} else {
vicsd_device = platform_device_alloc("moxart-sd", -1);
if (!vicsd_device) {
platform_driver_unregister(&vicsd_driver);
return -ENOMEM;
}
ret = platform_device_add(vicsd_device);
if (ret) {
platform_device_put(vicsd_device);
platform_driver_unregister(&vicsd_driver);
return ret;
}
printk("OK.\n");
}
return ret;
}
static void __exit vicsd_exit(void)
{
platform_driver_unregister(&vicsd_driver);
}
module_init(vicsd_init);
module_exit(vicsd_exit);