最近花時間研究了一下 MMC 卡驅動程式,開始在網上找了很多關于 MMC 卡驅動的分析文章,但大都是在描述各個層,這對于初學者來講幫助并不大,是以我就打算把自己的了解寫下來,希望對大家有用。個人覺得了解 LINUX 核心當中 MMC/SD 卡驅動程式構架是學習 MMC 卡驅動程式的重點,隻有了解了它的基本架構或流程才能真正了解一個塊裝置驅動程式的寫法,同時才能真正了解 LINUX 裝置驅動模型是如何發揮作用的。
一.需要的基礎知識:
- LINUX 裝置驅動的基本結構。
- 塊裝置驅動程式的基本構架(相信研究過 LDD3 當中的 sbull 的人應該都不成問題,如果隻是走馬觀花的話,那可得好好再補了)
- LINUX 裝置驅動模型。
二.驅動程式分析
首先,來明确一下我們需要分析的檔案。下面的檔案均來自 linux-2.6.24 源碼,我們重點是分析驅動程式的基本構架,是以不同核心版本的差異并不是很大。 MMC/SD 卡驅動程式位于 drivers/mmc 目錄下,我們隻列出我們分析過程涉及到的幾個檔案:
card/
block.c
queue.c/queue.h
core/
bus.c/bus.h
core.c/core.h
host.c/host.h
mmc.c
mmc_ops.c/mmc_ops.h 拿 MMC 卡來分析, SD卡驅動程式流程似。
host/
s3cmci.c/s3cmci.h 以 S3C24XX 的 MMC/SD 卡控制器為例,其它類型的控制器類似。
LINUX 當中對目錄的劃分是很有講究的,這些檔案被分布在 3 個目錄下,正好對應 MMC/SD 驅動程式的 3 個層次(關于層的劃分這裡浏覽一下,有個概念即可,當我們分析完了後再回頭來看,你會覺得很形象):
(1) 區塊層
主要是按照 LINUX 塊裝置驅動程式的架構實作一個卡的塊裝置驅動,這 block.c 當中我們可以看到寫一個塊裝置驅動程式時需要的 block_device_operations 結構體變量的定義,其中有 open/release/request 函數的實作,而 queue.c 則是對核心提供的請求隊列的封裝,我們暫時不用深入了解它,隻需要知道一個塊裝置需要一個請求隊列就可以了。
(2) 核心層
核心層封裝了 MMC/SD 卡的指令,例如存儲卡的識别,設定,讀寫。例如不管什麼卡都應該有一些識别,設定,和讀寫的指令,這些流程都是必須要有的,隻是具體對于不同的卡會有一些各自特有的操作。 Core.c 檔案是由 sd.c 、 mmc.c 兩個檔案支撐的, core.c 把 MMC 卡、 SD 卡的共性抽象出來,它們的差别由 sd.c 和 sd_ops.c 、 mmc.c 和 mmc_ops.c 來完成。
(3) 主機控制器層
主機控制器則是依賴于不同的平台的,例如 s3c2410 的卡控制器和 atmel 的卡控制器必定是不一樣的,是以要針對不同的控制器來實作。以 s3cmci.c 為例,它首先要進行一些設定,例如中斷函數注冊,全能控制器等等。然後它會向 core 層注冊一個主機( host ),用結構 mmc_host_ops 描述,這樣核心層就可以拿着這個 host 來操作 s3c24xx 的卡控制器了,而具體是 s3c24xx 的卡控制器還是 atmel 的卡控制器, core 層是不用知道的。
好了,對這幾個目錄有一個大概認識以後,我們來看幾個重要的資料結構:
struct mmc_host 用來描述卡控制器
struct mmc_card 用來描述卡
struct mmc_driver 用來描述 mmc 卡驅動
struct mmc_host_ops 用來描述卡控制器操作集,用于從主機控制器層向 core 層注冊操作函數,進而将 core 層與具體的主機控制器隔離。也就是說 core 要操作主機控制器,就用這個 ops 當中給的函數指針操作,不能直接調用具體主要制器的函數。
第一階段:
從 s3cmci_init 開始往下看
static int __init s3cmci_init(void)
{
platform_driver_register(&s3cmci_driver_2410);
}
有platfom_driver_register 函數,根據裝置模型的知識,我們知道那一定會有對應的 platform_device_register 函數的,可是在哪裡呢?沒有看到,那是不是這個 s3cmci_driver_2410 當中給的 probe 函數就不執行了???當然不是, mci 接口一般都是硬體做好的(我認為是這樣),是以在系統啟動時一定會有調用 platform_device_register 對闆上的資源進行注冊,如果沒有這個硬體資源,那我們這個驅動也就沒有用了。好,我們就假定是有 mci 接口的,而且也有與 s3cmci_driver_2410 對應的硬體資源注冊了,那自己就會去跑 probe 函數。來看一下 s3cmci_driver_2410:
static struct platform_driver s3cmci_driver_2410 = {
.driver.name = "s3c2410-sdi",
.probe = s3cmci_probe_2410,
.remove = s3cmci_remove,
.suspend = s3cmci_suspend,
.resume = s3cmci_resume,
};
我們到 s3cmci_probe_2410 函數中看,還是幹脆直接看 s3cmci_probe 算了:
static int s3cmci_probe(struct platform_device *pdev, int is2440) // 來自 /host/s3cmci.c
{
struct mmc_host *mmc;
struct s3cmci_host *host;
int ret;
……
mmc = mmc_alloc_host (sizeof(struct s3cmci_host), &pdev->dev);
if (!mmc) {
ret = -ENOMEM;
goto probe_out;
}
……
mmc->ops = &s3cmci_ops;
……
ret = mmc_add_host (mmc);
if (ret) {
dev_err(&pdev->dev, "failed to add mmc host./n");
goto free_dmabuf;
}
……
platform_set_drvdata(pdev, mmc);
return ;
……
}
這個函數很長,做的事件也很多,但我們關心的整個驅動的構架 / 流程,是以過濾掉一些細節的東西,隻看 2 個最重要的函數: mmc_alloc_host 、 mmc_add_host 。函數命名已經很形象了,前者是申請一個 mmc_host ,而後者是添加一個 mmc_host 。中間還有一個操作,就是給 mmc 的 ops 成員賦上了 s3cmci_ops 這個值。申請 mmc_host 當然很簡單,就是申請一個結構體(我們暫且這樣認為,因為他裡面還做的其它事情,後面會看到),而添加又是添加到哪裡去呢?看 mmc_add_host 函數:
int mmc_add_host(struct mmc_host *host) // 來自 core/host.c
{
int err;
……
err = device_add(&host->class_dev);
if (err)
return err;
mmc_start_host(host);
return ;
}
很簡單,就是增加了一個 device ,然後就調用 mmc_start_host 了,那就先跳過 device_add 這個動作,來看 mmc_start_host:
void mmc_start_host(struct mmc_host *host) // 來自 /host/core.c
{
mmc_power_off(host); // 掉電一下
mmc_detect_change(host, ); // ???
}
看上去隻有兩行代碼,不過濃縮才是精華, mmc_power_off(host) 光看名子都知道是在幹什麼,先跳過,來看 mmc_detect_change ,那麼它到底幹了些什麼呢?看一下就知道了:
void mmc_detect_change(struct mmc_host *host, unsigned long delay) // core/core.c
{
mmc_schedule_delayed_work(&host->detect, delay);
}
static int mmc_schedule_delayed_work(struct delayed_work *work, unsigned long delay)
{
return queue_delayed_work(workqueue, work, delay);
}
mmc_detect_change 又跳了一下,最後調用了 queue_delayed_work ,不知道這個函數功能的去查一下〈〈 LDD3 〉〉和〈〈深入了解 LINUX 核心〉〉,這幾個代碼告訴我們在 workqueue 這個工作隊列當中添加一個延遲的工作任務,而這個工作任務就是由 host->detect 來描述的,在随後的 delay 個 jiffies 後會有一個記錄在 host->detect 裡面的函數被執行,那麼到這裡 s3cmci_probe 這個函數算是結束了,但事情還沒有完, workqueue 這個工作隊列還在忙,不一會兒它就會調用 host->detect 裡面那個函數,這個函數到底是哪個函數,到底是用來幹什麼的呢?好像沒有看到, detect 包含在 host 裡面,那估計是在剛才那個申請的地方設定的那個函數,回過頭來看一下 mmc_alloc_host:
struct mmc_host *mmc_alloc_host(int extra, struct device *dev) // 來自 core/host.c
{
struct mmc_host *host;
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
if (!host)
return NULL;
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
return host;
}
如果你看了 queue_delayed_work 這個函數功能介紹,相信對 INIT_DELAYED_WORK 也不會陌生了吧。不廢話了,來看mmc_rescan:
void mmc_rescan(struct work_struct *work) // // 來自 core/host.c
{
struct mmc_host *host = container_of(work, struct mmc_host, detect.work);
u32 ocr;
int err;
……
/* detect a newly inserted card */
……
/*
* First we search for SDIO...
*/
err = mmc_send_io_op_cond(host, , &ocr);
if (!err) {
if (mmc_attach_sdio(host, ocr))
mmc_power_off(host);
goto out;
}
/*
* ...then normal SD...
*/
err = mmc_send_app_op_cond(host, , &ocr);
if (!err) {
if (mmc_attach_sd(host, ocr))
mmc_power_off(host);
goto out;
}
/*
* ...and finally MMC.
*/
err = mmc_send_op_cond(host, , &ocr);
if (!err) {
if (mmc_attach_mmc(host, ocr))
mmc_power_off(host);
goto out;
}
mmc_release_host(host);
mmc_power_off(host);
out:
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
}
浏覽一個這個函數,看看函數名,再看看注釋,知道什麼了嗎?它是在檢測是不是有卡插入了卡控制器,如果有卡挺入就要采取相應的行動了。這裡要明白一點,我們平時用的 SD/MMC 卡就是一個卡,如果要操作它得用 SD/MMC 卡控制器才行,是以可以看到有 struct mmc_card,struct mmc_host 的區分。
到這裡了,來回憶一下 s3cmci_probe 這個函數做的事情,大概就是準備一個 mmc_host 結構,然後添加一個主要制器裝置到核心,最後又調用了一下 mmc_rescan 來檢測是不是有卡插入了。
如果有卡插入了還好,可以去操作卡了,那如果沒有卡插入呢? mmc_rescan 不是白調用了一次嗎?是啊,的确是白調用了一次。可是卡插入時為什麼 PC 還是能檢測到呢?看來卡檢測的動作不光是在 probe 的最後一步做了一次,其它地方也有做。卡插入一般都是人為地随時去插入的,像這種情況一般都是會用中斷機制去提供系統有外來侵入,然後再去采取行動。 SD/MMC 卡也的确是這樣做的,找來找去,發現在 s3cmci_probe 裡面注冊了一個中斷函數 s3cmci_irq_cd( 函數名的意思應該是 irq card detect) ,就是這個了,看看這個函數先:
static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id) // host/s3cmci.c
{
struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
mmc_detect_change(host->mmc, msecs_to_jiffies());
return IRQ_HANDLED;
}
看到這個函數想都不用想,直接跳到 mmc_rescan 裡面去看就行了。前面已經知道了 mmc_rescan 裡面就是在檢測卡是不是插入了,既然卡随時插入我們都能檢測到了,那就來看卡插入後都做了些什麼動作吧。
第二階段:
mmc_rescan 裡面既要檢測 sd 卡,又要檢測 mmc 卡的,我們就照着一個往下走,假定有個人插入了 MMC 卡,那就應該走下面這幾行:
err = mmc_send_op_cond(host, , &ocr);
if (!err) {
if (mmc_attach_mmc(host, ocr))
mmc_power_off(host);
goto out;
}
mmc_send_op_cond 這個函資料說是讀了一下卡的什麼值,這個值是什麼意義我也不清楚,這就像檢測 FLASH 時讀 FLASH 的 ID 一樣,網卡也是這樣的,不用管這個值的意義了,隻要知道它能辨別是一個 MMC 卡插入就行了。如果取這個值沒有錯誤的話就得進 mmc_attach_mmc 了:
/*
* Starting point for MMC card init.
*/
int mmc_attach_mmc(struct mmc_host *host, u32 ocr) // core/mmc.c
{
int err;
……
mmc_attach_bus_ops(host); // 這個與總線的電源管理有關,暫時跳過
/*
* Detect and init the card.
*/
err = mmc_init_card(host, host->ocr, NULL);
if (err)
goto err;
……
mmc_release_host(host);
err = mmc_add_card(host->card);
if (err)
goto remove_card;
return ;
remove_card:
……
err:
……
return err;
}
還是找幾個關鍵函數來看 mmc_init_card 從函數名來看就是初始化一個 card ,這個 card 就用 struct mmc_card 結構來描述,然後又調用 mmc_add_card 将卡裝置添加到了核心,先來看 mmc_init_card 都做了些什麼事情:
static int mmc_init_card(struct mmc_host *host, u32 ocr,
struct mmc_card *oldcard)
{
struct mmc_card *card;
int err;
u32 cid[];
unsigned int max_dtr;
……
/*
* Allocate card structure.
*/
card = mmc_alloc_card(host, &mmc_type);
if (IS_ERR(card)) {
err = PTR_ERR(card);
goto err;
}
card->type = MMC_TYPE_MMC;
card->rca = ;
memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
……
host->card = card;
return ;
free_card:
……
err:
……
return err;
}
将與硬體操作相關的全部删掉,最後對我們有用的也就這幾行了 mmc_alloc_card 申請了一個 struct mmc_card 結構,然後給 card->type 賦上 MMC_TYPE_MMC ,最後将 card 又賦給了 host->card ,這和具體硬體還是挺像的,因為一個主要制器一般就插一個卡,有卡時 host->card 有值,沒有卡時 host->card 自己就是 NULL 了。
鑽進 mmc_alloc_card 裡面來看看:
/*
* Allocate and initialise a new MMC card structure.
*/
struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type)
{
struct mmc_card *card;
card = kzalloc(sizeof(struct mmc_card), GFP_KERNEL);
if (!card)
return ERR_PTR(-ENOMEM);
card->host = host;
device_initialize(&card->dev);
card->dev.parent = mmc_classdev(host);
card->dev.bus = &mmc_bus_type;
card->dev.release = mmc_release_card;
card->dev.type = type;
return card;
}
struct mmc_card 結構裡面包含了一個 struct device 結構, mmc_alloc_card 不但申請了記憶體,而且還填充了 struct device 中的幾個成員,尤其 card->dev.bus = &mmc_bus_type; 這一句要重點對待。
申請一個 mmc_card 結構,并簡單初始化後, mmc_init_card 的使命就完成了,然後再調用 mmc_add_card 将這個 card 裝置添加到核心。 mmc_add_card 其實很簡單,就是調用 device_add 将 card->dev 添加到核心當中去。
知道總線模型這個東西的人都明白,理到 device_add 裡面總線就應該有動作了,具體是哪個總線呢?那就得看你調用 device_add 時送的那個 dev 裡面指定的是哪個總線了,我們送的 card->dev ,那麼 card->dev.bus 具體指向什麼呢?很明現是那個 mmc_bus_type :
static struct bus_type mmc_bus_type = {
.name = "mmc",
.dev_attrs = mmc_dev_attrs,
.match = mmc_bus_match,
.uevent = mmc_bus_uevent,
.probe = mmc_bus_probe,
.remove = mmc_bus_remove,
.suspend = mmc_bus_suspend,
.resume = mmc_bus_resume,
};
在 device_add 裡面,裝置對應的總線會拿着你這個裝置和挂在這個總線上的所有驅動程式去比對( match ),此時會調用 match 函數,如果比對到了就會調用總線的 probe 函數或驅動的 probe 函數,那我們看一下這裡的 mmc_bus_match 是如何進行比對的:
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
{
return ;
}
看來 match 永遠都能成功,那就去執行 probe 吧:
static int mmc_bus_probe(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = dev_to_mmc_card(dev);
return drv->probe(card);
}
這裡就有點麻煩了,在這個函數裡面又調用了一下 drv->probe() ,那這個 drv 是什麼呢?上面
struct mmc_driver*drv=to_mmc_driver(dev->driver);
match 函數總是傳回 1 ,那看來隻要是挂在這條總線上的 driver 都有可能跑到這裡來了,事實的确也是這樣的,不過好在挂在這條總線上的 driver 隻有一個,它是這樣定義的:
static struct mmc_driver mmc_driver = {
.drv = {
.name = "mmcblk",
},
.probe = mmc_blk_probe,
.remove = mmc_blk_remove,
.suspend = mmc_blk_suspend,
.resume = mmc_blk_resume,
};
看到這裡時, card/core/host 幾個已經全部被扯進來了,邊看 mmc_driver 中的幾個函數,他們幾個如何聯系起來也就慢慢明白了。那我們繼續吧。
第三階段:
前面已經看到了,在總線的 probe 裡面調用了 drv->probe, 而這個函數就對應的是 mmc_blk_probe ,具體這個 mmc_driver 是怎麼挂到 mmc_bus 上的,自己去看 mmc_blk_init() ,就幾行代碼,應該不難。
static int mmc_blk_probe(struct mmc_card *card) // 來自 card/block.c
{
struct mmc_blk_data *md;
int err;
……
md = mmc_blk_alloc(card);
if (IS_ERR(md))
return PTR_ERR(md);
……
add_disk(md->disk);
return ;
out:
mmc_blk_put(md);
return err;
}
還是撿重要的函數看,一看到這個函數最後調用了 add_disk ,你應該可以想到些什麼吧?如果你不知道我在說些什麼,那我估計你沒有看過 LDD3 ,或者看了也是走馬觀花了。我來告訴你:如果看到 add_disk ,那說明前面一定會有 alloc_disk 和初始化隊列的動作,在 mmc_blk_probe 時面沒有展現出來,那就看 mmc_blk_alloc(card) 那一行:
static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card)
{
struct mmc_blk_data *md;
int devidx, ret;
devidx = find_first_zero_bit(dev_use, MMC_NUM_MINORS);
if (devidx >= MMC_NUM_MINORS)
return ERR_PTR(-ENOSPC);
__set_bit(devidx, dev_use);
md = kzalloc(sizeof(struct mmc_blk_data), GFP_KERNEL);
if (!md) {
ret = -ENOMEM;
goto out;
}
/*
* Set the read-only status based on the supported commands
* and the write protect switch.
*/
md->read_only = mmc_blk_readonly(card);
md->disk = alloc_disk( << MMC_SHIFT);
if (md->disk == NULL) {
ret = -ENOMEM;
goto err_kfree;
}
spin_lock_init(&md->lock);
md->usage = ;
ret = mmc_init_queue(&md->queue, card, &md->lock);
if (ret)
goto err_putdisk;
md->queue.issue_fn = mmc_blk_issue_rq;
md->queue.data = md;
md->disk->major = MMC_BLOCK_MAJOR;
md->disk->first_minor = devidx << MMC_SHIFT;
md->disk->fops = &mmc_bdops;
md->disk->private_data = md;
md->disk->queue = md->queue.queue;
md->disk->driverfs_dev = &card->dev;
/*
* 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.
*/
sprintf(md->disk->disk_name, "mmcblk%d", devidx);
blk_queue_logical_block_size(md->queue.queue, );
if (!mmc_card_sd(card) && mmc_card_blockaddr(card)) {
/*
* The EXT_CSD sector count is in number or 512 byte
* sectors.
*/
set_capacity(md->disk, card->ext_csd.sectors);
} else {
/*
* The CSD capacity field is in units of read_blkbits.
* set_capacity takes units of 512 bytes.
*/
set_capacity(md->disk,
card->csd.capacity << (card->csd.read_blkbits - ));
}
return md;
err_putdisk:
put_disk(md->disk);
err_kfree:
kfree(md);
out:
return ERR_PTR(ret);
}
看到這個函數的代碼,我們自然就回憶起了塊裝置驅動的整個套路了:
- 配置設定、初始化請求隊列,并綁定請求隊列和請求函數。
- 配置設定,初始化 gendisk ,給 gendisk 的 major , fops , queue 等成員指派,最後添加 gendisk 。
- 注冊塊裝置驅動。
我們看看 MMC 卡驅動程式有沒有按這個套路走,
- mmc_init_queue 初始了隊列,并将 mmc_blk_issue_rq; 函數綁定成請求函數;
- alloc_disk 配置設定了 gendisk 結構,并初始化了 major , fops ,和 queue ;
- 最後調用 add_disk 将塊裝置加到 KERNEL 中去。
到這裡雖然 mmc_blk_probe 已經結束了,但我們别停下來。記得 LDD3 上在講 sbull 執行個體時說過, add_disk 的調用标志着一個塊裝置驅動将被激活,是以在這之前必須把其它所有準備工作全部做好,作者為什麼會這樣說是有理由的,因為在 add_disk 裡面 kernel 會去調用你綁定到隊列中的請求函數,目的是去你的塊裝置上讀分區表。而且是在 add_disk 内部就要做的,而不是 add_disk 傳回後再做,具體為什麼會這樣,去看 add_disk 的代碼實作就知道了。
既然要調用請求函數去讀,那我們就來看看請求函數: mmc_blk_issue_rq
static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
{
struct mmc_blk_data *md = mq->data;
struct mmc_card *card = md->queue.card;
struct mmc_blk_request brq;
int ret = , disable_multi = ;
do {
mmc_wait_for_req(card->host, &brq.mrq);
/*
* A block was successfully transferred.
*/
spin_lock_irq(&md->lock);
ret = __blk_end_request(req, , brq.data.bytes_xfered);
spin_unlock_irq(&md->lock);
} while (ret);
return ;
}
這個函數實在太長了,好在我們不用全部看,大部分讀資料的準備代碼和出錯處理的代碼已經被我删掉了,隻要知道讀資料都是在這裡完成的就夠了。看不懂這個函數的,拿上 LDD3 找個人少的地方,将 sbull 研究透了也就明白這個函數了。不過這個函數裡涉及的東西還挺不少,“散清單”,“回彈”都在這裡出現了,有時間慢慢去研究吧。
在塊裝置驅動當中你隻需要抓住請求隊列和請求函數就可以了,具體那些 block_device_operations 裡面指派的函數可不像字元裝置驅動裡面那麼受關注了。
分析到這裡, MMC/SD 卡的驅動整個構架基本也就很明析了,說簡單了就是做了兩件事:
- 卡的檢測;
- 卡資料的讀取。
最後再将這兩個過程大概串一下:
- 卡的檢測:
s3cmci_probe(host/s3cmci.c)
mmc_alloc_host(core/core.c)
mmc_rescan(core/core.c)
mmc_attach_mmc(core/mmc.c)
mmc_init_card(core/mmc.c)
mmc_add_card(core/bus.c)
device_add
mmc_bus_match(core/bus.c)
mmc_bus_probe(core/bus.c)
mmc_blk_probe(card/block.c)
alloc_disk/add_disk
- 讀寫資料:
mmc_blk_issue_rq ( card/block.c )
mmc_wait_for_req(core/core.c)
mmc_start_request(core/core.c)
host->ops->request(host, mrq) // s3cmci 中 s3cmci_request
MMC/SD 卡的驅動分析完了,是不是有些複雜,不過這樣設計的目的是為了分層,讓具體平台的驅動編寫更加省事。
本文來自CSDN部落格,轉載請标明出處:http://blog.csdn.net/mmmmpl/archive/2010/09/22/5900760.aspx