歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> Linux技術

Linux SD卡驅動開發(六) —— SD卡啟動過程總體分析

一、工作流程
mmc驅動主要文件包括
drivers/mmc/card/block.c
drivers/mmc/card/queue.c
drivers/mmc/core/core.c
drivers/mmc/core/host.c
drivers/mmc/core/
內核啟動時,首先執行core/core.c的mmc_init,注冊mmc、sd總線,以及一個host class設備。接著執行card/block.c中,申請一個塊設備。
二、數據結構:
這裡涉及三種總線
[cpp] view
plain copy





1. platform bus //MMC host controller 作為一種 platform device, 它是需要注冊到 platform bus上 的
driver/base/platform.c
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
2. mmc bus type //在mmc_init()中被創建的.通過調用 mmc_register_bus() 來注冊 MMC 總線
drivers\mmc\core\bus.c
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,
.shutdown = mmc_bus_shutdown,
.pm = &mmc_bus_pm_ops,
};
3. sdio bus type //在mmc_init()中被創建的.通過調用sdio_register_bus() 來注冊 SDIO 總線
drivers\mmc\core\sdio_bus.c
static struct bus_type sdio_bus_type = {
.name = "sdio",
.dev_attrs = sdio_dev_attrs,
.match = sdio_bus_match,
.uevent = sdio_bus_uevent,
.probe = sdio_bus_probe,
.remove = sdio_bus_remove,
.pm = SDIO_PM_OPS_PTR,
};
其中mmc總線操作相關函數,由於mmc卡支持多種總數據線,如SPI、SDIO、8LineMMC而不同的總線的操作控制方式不盡相同,所以通過此結構與相應的總線回調函數相關聯。
[cpp] view
plain copy





//總線操作結構
struct mmc_bus_ops {
void (*remove)(struct mmc_host *);
void (*detect)(struct mmc_host *);
int (*sysfs_add)(struct mmc_host *, struct mmc_card *card);
void (*sysfs_remove)(struct mmc_host *, struct mmc_card *card);
void (*suspend)(struct mmc_host *);
void (*resume)(struct mmc_host *);
};
// mmc卡的總線操作 core/mmc.c
static const struct mmc_bus_ops mmc_ops = {
.remove = mmc_remove,
.detect = mmc_detect,
.sysfs_add = mmc_sysfs_add,
.sysfs_remove = mmc_sysfs_remove,
.suspend = mmc_suspend,
.resume = mmc_resume,
};
// sd卡的總線操作 core/sd.c
static const struct mmc_bus_ops mmc_sd_ops = {
.remove = mmc_sd_remove,
.detect = mmc_sd_detect,
.sysfs_add = mmc_sd_sysfs_add,
.sysfs_remove = mmc_sd_sysfs_remove,
.suspend = mmc_sd_suspend,
.resume = mmc_sd_resume,
};
// sdio的總線操作 core/sdio.c
static const struct mmc_bus_ops mmc_sdio_ops = {
.remove = mmc_sdio_remove,
.detect = mmc_sdio_detect,
};
關於總線操作的函數:
.detect,驅動程序經常需要調用此函數去檢測mmc卡的狀態,具體實現是發送CMD13命令,並讀回響應,如果響應錯誤,則依次調用.remove、detach_bus來移除卡及釋放總線。
三、總體架構
1、kernel啟動時,先後執行mmc_init()及mmc_blk_init(),以對mmc設備及mmc塊模塊進行初始化
[cpp] view
plain copy





mmc/core/core.c
static int __init mmc_init(void)
workqueue = alloc_ordered_workqueue("kmmcd", 0);//建立了一個工作隊列workqueue,這個工作隊列的作用主要是用來支持熱插拔
ret = mmc_register_bus();//注冊一個mmc總線
ret = mmc_register_host_class();//注冊了一個 mmc_host 類
ret = sdio_register_bus();//注冊了一個 sdio_bus_type
*******
mmc/card/block.c
static int __init mmc_blk_init(void)
res = register_blkdev(MMC_BLOCK_MAJOR, "mmc");//注冊一個塊設備
res = mmc_register_driver(&mmc_driver);//注冊一個mmc設備驅動
static struct mmc_driver mmc_driver =
.probe = mmc_blk_probe,
static int mmc_blk_probe(struct mmc_card *card)
mmc_set_bus_resume_policy(card->host, 1);//*host 該指針指向一個mmc主機實例,塊設備中的讀寫操作就是調用這個mmc主機的操作函數host->ops->request來實現對實際硬件的操作。
2、core部分會做兩件事
a -- 取得總線
b -- 檢查總線操作結構指針bus_ops,如果為空,則重新利用各總線對端口進行掃描,檢測順序依次為:SDIO、Normal SD、MMC。當檢測到相應的卡類型後,就使用mmc_attach_bus()把相對應的總線操作與host連接起來
[cpp] view
plain copy





void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
{
...
host->bus_ops = ops;
...
}
3、然後在掛載mmc設備驅動時,執行驅動程序中的xx_mmc_probe(),檢測host設備中掛載的sd設備
[cpp] view
plain copy





kernel\arch\arm\configs\msm9625_defconfig
CONFIG_MMC_MSM=y
kernel\drivers\mmc\host\Makefile
obj-$(CONFIG_MMC_MSM) += msm_sdcc.o
msm_sdcc.c (drivers\mmc\host)
//系統初始化時掃描 platform 總線上是否有名為該SD主控制器名字"msm_sdcc"的設備,如果有, 驅動程序將主控制器掛載到 platform 總線上,並注冊該驅動程序
static int __init msmsdcc_init(void)
platform_driver_register(&msmsdcc_driver); //注冊 platform driver
static struct platform_driver msmsdcc_driver = {
.probe = msmsdcc_probe,
.remove = msmsdcc_remove,
.driver = {
.name = "msm_sdcc",
.pm = &msmsdcc_dev_pm_ops,
.of_match_table = msmsdcc_dt_match,
},
};
//整個設備驅動的 probe()函數,其本質就是是為設備建立起數據結構並對其賦初值
//msmsdcc_probe 所有賦值中,我們重點關注從 platform_device *pdev裡得到的數據,即設備樹裡的數據
//platform_device *pdev是在系統初始化的時候掃描 platform 總線發現SD主控制器後所得到的數據
static int msmsdcc_probe(struct platform_device *pdev)
{
//初始化設備的數據結構
if (pdev->dev.of_node) {
plat = msmsdcc_populate_pdata(&pdev->dev); //獲取設備樹信息
of_property_read_u32((&pdev->dev)->of_node,"cell-index", &pdev->id);
} else {
plat = pdev->dev.platform_data;
}
//為主設備控制器建立數據結構,建立kobject,並初始化等待隊列,工作隊列,以及一些控制器的配置
mmc = mmc_alloc_host(sizeof(struct msmsdcc_host), &pdev->dev); ---- 1
//實現設備驅動的功能函數,如mmc->ops = &pxamci_ops;
mmc->ops = &msmsdcc_ops;
//申請中斷函數 request_irq()
ret = request_irq(core_irqres->start, msmsdcc_irq, IRQF_SHARED,DRIVER_NAME " (cmd)", host);
//注冊設備,即注冊kobject,建立sys文件,發送uevent等
mmc_add_host(mmc); ---- 2
//其他需求,如在/proc/driver下建立用戶交互文件等
ret = device_create_file(&pdev->dev, &host->auto_cmd21_attr);
}
4、此時probe函數會創建一個host設備,然後開啟一個延時任務mmc_rescan()
[cpp] view
plain copy





1:
core/host.c
//重要函數mmc_alloc_host , 用於分配mmc_host結構體指針的內存空間大小
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)----創建一個 mmc_host 和 mmc_spi_host ,且mmc_host的最後一個成員指針private指向mmc_spi_host
//建立數據結構
struct mmc_host *host;
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
//建立kobject
host->parent = dev;
host->class_dev.parent = dev;
host->class_dev.class = &mmc_host_class;
device_initialize(&host->class_dev);
//初始化等待隊列,工作隊列
init_waitqueue_head(&host->wq);
INIT_DELAYED_WORK(&host->detect, mmc_rescan); //建立了一個工作隊列任務 structdelayed_work detect。工作隊列任務執行的函數為mmc_rescan
//配置控制器
host->max_segs = 1;
host->max_seg_size = PAGE_CACHE_SIZE;
return host;
5、驅動掛載成功後,mmc_rescan()函數被執行,然後對卡進行初始化(步驟後面詳細講述)
[cpp] view
plain copy





core/core.c
//mmc_rescan 函數是需要重點關注的,因為SD卡協議中的檢測,以及卡識別等都是在此函數中實現
void mmc_rescan(struct work_struct *work)
if (host->bus_ops && host->bus_ops->detect && !host->bus_dead && !(host->caps & MMC_CAP_NONREMOVABLE)) //存在熱插拔卡,不包括emmc,調用探測函數
host->bus_ops->detect(host);
mmc_bus_put(host); //減少引用技術,就釋放
mmc_bus_get(host); //增加bus引用計數
if (host->bus_ops != NULL) {
mmc_bus_put(host); //如果卡仍然存在,減少引用計數,不必探測了
goto out;
}
if (host->ops->get_cd && host->ops->get_cd(host) == 0) //有卡,退出
goto out;
mmc_claim_host(host); //用於檢測host是否被占用,占用則退出,否則標記成占用
if (!mmc_rescan_try_freq(host, host->f_min))
初始化卡接以下流程初始化:
a、發送CMD0使卡進入IDLE狀態
b、發送CMD8,檢查卡是否SD2.0。SD1.1是不支持CMD8的,因此在SD2.0 Spec中提出了先發送CMD8,如響應為無效命令,則卡為SD1.1,否則就是SD2.0(請參考SD2.0 Spec)。
c、發送CMD5讀取OCR寄存器。
d、發送ACMD55、CMD41,使卡進入工作狀態。MMC卡並不支持ACMD55、CMD41,如果這步通過了,則證明這張卡是SD卡。
e、如果d步驟錯誤,則發送CMD1判斷卡是否為MMC。SD卡不支持CMD1,而MMC卡支持,這就是SD和MMC類型的判斷依據。
f、如果ACMD41和CMD1都不能通過,那這張卡恐怕就是無效卡了,初始化失敗。
假如掃描到總線上掛有有效的設備,就調用相對應的函數把設備裝到系統中,mmc_attach_sdio()、mmc_attach_sd()、mmc_attach_mmc()這三個函數分別是裝載sdio設備,sd卡和mmc卡的。
在 sd卡中,驅動循環發送ACMD41、CMD55給卡,讀取OCR寄存器,成功後,依次發送CMD2(讀CID)、CMD3(得到RCA)、CMD9(讀 CSD)、CMD7(選擇卡)。後面還有幾個命令分別是ACMD41&CMD51,使用CMD6切換一些功能,如切換到高速模式。
經過上述步驟,已經確定當前插入的卡是一張有效、可識別的存儲卡。然後調用mmc_add_card()把存儲卡加到系統中。正式與系統驅動連接在一起
[cpp] view
plain copy





static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
host->f_init = freq; //設置某一個時鐘頻率
mmc_power_up(host); //與 mmc_power_off 類似,不過設置了啟動時需要的 ios
mmc_go_idle(host); ----1a //CMD0 ,SD卡從 inactive 到 idle
mmc_send_if_cond(host, host->ocr_avail);//檢測SD卡是否支持SD2.0
if (!mmc_attach_sd(host)) ----1b //然後對mmc或者sd發送一些命令進行探測,這裡以 sd 為例
1a:
int mmc_go_idle(struct mmc_host *host)
struct mmc_command cmd = {0};
cmd.opcode = MMC_GO_IDLE_STATE; //即CMD0
cmd.arg = 0; //此命令無參數
err = mmc_wait_for_cmd(host, &cmd, 0)
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
memset(cmd->resp, 0, sizeof(cmd->resp)); //調用了 mmc_start_request,
cmd->retries = retries;
mrq.cmd = cmd;
mmc_wait_for_req(host, &mrq);
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq) ----重要函數
__mmc_start_req(host, mrq);
static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq)
mmc_start_request(host, mrq);
static void mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
host->ops->request(host, mrq); //即 msmsdcc_request, MMC 核心與核HOST 層握手了
1b:
core/mmc.c
int mmc_attach_sd(struct mmc_host *host) //完成匹配,和初始化卡的功能
err = mmc_send_app_op_cond(host, 0, &ocr); ----1b1 //檢測是否是支持SD卡
host->ocr = mmc_select_voltage(host, ocr); //設置MMC電壓
err = mmc_init_card(host, host->ocr, NULL); //對mmc卡進行初始化,主要是讀取mmc卡裡的一些寄存器信息,且對這些寄存器的值進行設置
err = mmc_sd_init_card(host, host->ocr, NULL); ----1b2
err = mmc_add_card(host->card); ----1b3 //調用 mmc_add_card 來把 mmc_card 掛載到 mmc_bus_type 總線去
1b1:
int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
cmd.opcode = SD_APP_OP_COND; //ACMD41,獲取 SDcard 的允許電壓范圍值,保存在 ocr 中. 所有發送它之前需要發送 CMD_55 命令。執行完後 card 狀態變為 READY
1b2:
static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,struct mmc_card *oldcard)
err = mmc_sd_get_cid(host, ocr, cid, &rocr); //發送 CMD2 ,獲取卡的身份信息,進入到身份狀態
card = mmc_alloc_card(host, &sd_type); //分配一張 SD 類型的 card 結構
err = mmc_send_relative_addr(host, &card->rca); //獲取卡的相對地址,注意一前卡和主機通信都采用默認地址,現在有了自己的地址了,進入到 stand_by 狀態
err = mmc_sd_get_csd(host, card); ----mmc_send_csd(card, card->raw_csd);//CMD9, 獲取 CSD 寄存器的信息,包括 block 長度,卡容量等信息
err = mmc_select_card(card); //發送 CMD7, 選中目前 RADD 地址上的卡,任何時候總線上只有一張卡被選中,進入了傳輸狀態
err = mmc_sd_setup_card(host, card, oldcard != NULL);
int mmc_sd_setup_card(struct mmc_host *host, struct mmc_card *card,bool reinit)
mmc_app_send_scr(card, card->raw_scr); //發送命令 ACMD51 獲取 SRC 寄存器的內容,進入到 SENDING-DATA 狀態
if (host->ops->get_ro(host) > 0 ) // get_ro(host) 即是 msmsdcc_get_ro
mmc_card_set_readonly(card); //是否寫保護,如果是的,將 card 狀態設置為只讀狀態
1b3:
core/bus.c
int mmc_add_card(struct mmc_card *card) // /sys/devices/msm_sdcc.2/mmc_host/mmc0
ret = device_add(&card->dev);
drivers/base/core.c
int device_add(struct device *dev)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id); //
bus_probe_device(dev);
void bus_probe_device(struct device *dev)
if (bus->p->drivers_autoprobe)
ret = device_attach(dev); //這樣,在總線 mmc_bus_type 中就有了 mmc 設備 mmc_card 了
***********
2:
//完成kobject的注冊,並調用 mmc_rescan,目的在於在系統初始化的時候就掃描SD總線查看是否存在SD卡
int mmc_add_host(struct mmc_host *host)
err = device_add(&host->class_dev);//將設備注冊進linux設備模型,最終的結果就是在 sys/bus/platform/devices 目錄下能見到 mmc 設備節點
mmc_start_host(host);
void mmc_start_host(struct mmc_host *host)
mmc_power_off(host); ----2a
mmc_detect_change(host, 0); ----2b
2a:
void mmc_power_off(struct mmc_host *host)
host->ios.power_mode = MMC_POWER_OFF; //對 ios 進行了設置
...
mmc_set_ios(host);
void mmc_set_ios(struct mmc_host *host)
host->ops->set_ios(host, ios); // set_ios 實際上就是 mmc_host_ops 的 .set_ios = msmsdcc_set_ios,
2b:
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
mmc_schedule_delayed_work(&host->detect, delay); //實際上就是調用我們前面說的延時函數 mmc_rescan
6、卡設備加到系統中後,通知mmc塊設備驅動。塊設備驅動此時調用probe函數,即mmc_blk_probe()函數,mmc_blk_probe()首 先分配一個新的mmc_blk_data結構變量,然後調用mmc_init_queue,初始化blk隊列。然後建立一個線程 mmc_queue_thread()
7、然後就可以進行傳輸命令和數據了
[cpp] view
plain copy





struct mmc_host_ops {
//用於SD卡命令的傳輸,比如發送和接收命令,CMD0,CMD8,ACMD41諸如此類的都是在這個函數去實現
void (*request)(struct mmc_host *host, struct mmc_request *req);
}
static const struct mmc_host_ops msmsdcc_ops = {
.enable = msmsdcc_enable,
.disable = msmsdcc_disable,
.pre_req = msmsdcc_pre_req,
.post_req = msmsdcc_post_req,
.request = msmsdcc_request,
.set_ios = msmsdcc_set_ios,
.get_ro = msmsdcc_get_ro,
.enable_sdio_irq = msmsdcc_enable_sdio_irq,
.start_signal_voltage_switch = msmsdcc_switch_io_voltage,
.execute_tuning = msmsdcc_execute_tuning,
.hw_reset = msmsdcc_hw_reset,
.stop_request = msmsdcc_stop_request,
.get_xfer_remain = msmsdcc_get_xfer_remain,
.notify_load = msmsdcc_notify_load,
};
/*這個函數實現了命令和數據的發送和接收,
當 CORE 部分需要發送命令或者傳輸數據時,都會調用這個函數,並傳遞 mrq 請求*/
static void msmsdcc_request(struct mmc_host *mmc, struct mmc_request *mrq)
mmc_request_done(mmc, mrq); // 如果卡不存在,就終止請求
msmsdcc_request_start(host, mrq);
static void msmsdcc_request_start (struct msmsdcc_host *host, struct mmc_request *mrq)
if ((mrq->data->flags & MMC_DATA_READ) ||host->curr.use_wr_data_pend) //判斷發送數據還是命令
msmsdcc_start_data(host, mrq->data,mrq->sbc ? mrq->sbc : mrq->cmd,0); //發送數據
else
msmsdcc_start_command(host,mrq->sbc ? mrq->sbc : mrq->cmd,0); //發送命令
static void msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data,struct mmc_command *cmd, u32 c)
//對某些 寄存器進行設置, 使能某些中斷, 如 pio_irqmask
...
if (is_dma_mode(host) && (datactrl & MCI_DPSM_DMAENABLE)) //采用 DMA 進行數據傳輸還是采用 FIFO 進行數據傳輸
msmsdcc_start_command_deferred(host, cmd, &c); //啟動了數據傳輸模式
else
msmsdcc_start_command(host, cmd, c)
static void msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd, u32 c)
{
msmsdcc_start_command_deferred(host, cmd, &c);
msmsdcc_start_command_exec(host, cmd->arg, c);
}
static void msmsdcc_start_command_deferred(struct msmsdcc_host *host,struct mmc_command *cmd, u32 *c)
cmd->opcode ----對應SD卡命令 ,如 CMD0:復位SD 卡
Copyright © Linux教程網 All Rights Reserved