MMC卡驱动分析

MMC卡驱动分析MMC卡驱动分析 最近花时间研究了一下MMC卡驱动程序,开始在网上找了很多关于MMC卡驱动的分析文章,但大都是在描述各个层,这对于初学者来讲帮助并不大,所以我就打算把自己的理解写下来,希望对大家有用。个人觉得理解LINUX内核当中MMC/SD卡驱动程序构架是学习MMC卡驱动程序的重点,只有理解了它的基本框架或流程才能真正理解一个块设备驱动程序的写法,同时才能真正理

大家好,又见面了,我是你们的朋友全栈君。

MMC 卡驱动分析

 

最近花时间研究了一下 MMC 卡驱动程序,开始在网上找了很多关于 MMC 卡驱动的分析文章,但大都是在描述各个层,这对于初学者来讲帮助并不大,所以我就打算把自己的理解写下来,希望对大家有用。个人觉得理解 LINUX 内核当中 MMC/SD 卡驱动程序构架是学习 MMC 卡驱动程序的重点,只有理解了它的基本框架或流程才能真正理解一个块设备驱动程序的写法,同时才能真正理解 LINUX 设备驱动模型是如何发挥作用的。

 

一.需要的基础知识:

1.       LINUX 设备驱动的基本结构。

2.       块设备驱动程序的基本构架(相信研究过 LDD3 当中的 sbull 的人应该都不成问题,如果只是走马观花的话,那可得好好再补补了)

3.       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);

}

 

有 platform_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 0;

……

}

 

这个函数很长,做的事件也很多,但我们关心的整个驱动的构架 / 流程,所以过滤掉一些细节的东西,只看 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 0;

}

 

很简单,就是增加了一个 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, 0);              // ???

}

看上去只有两行代码,不过浓缩才是精华, 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

// 来自 core/host.c

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, 0, &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, 0, &ocr);

       if (!err) {

              if (mmc_attach_sd(host, ocr))

                     mmc_power_off(host);

              goto out;

       }

 

       /*

         * …and finally MMC.

         */

       err = mmc_send_op_cond(host, 0, &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(500));

 

       return IRQ_HANDLED;

}

看到这个函数想都不用想,直接跳到 mmc_rescan 里面去看就行了。前面已经知道了 mmc_rescan 里面就是在检测卡是不是插入了,既然卡随时插入我们都能检测到了,那就来看卡插入后都做了些什么动作吧。

 

第二阶段:

       mmc_rescan 里面既要检测 sd 卡,又要检测 mmc 卡的,我们就照着一个往下走,假定有个人插入了 MMC 卡,那就应该走下面这几行:

       err = mmc_send_op_cond(host, 0, &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 0;

 

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[4];

       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 = 1;

              memcpy(card->raw_cid, cid, sizeof(card->raw_cid));

……

              host->card = card;

 

       return 0;

 

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 1;

}

看来 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 0;

 

  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(1 << MMC_SHIFT);

       if (md->disk == NULL) {

              ret = -ENOMEM;

              goto err_kfree;

       }

 

       spin_lock_init(&md->lock);

       md->usage = 1;

 

       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, 512);

 

       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 – 9));

       }

       return md;

 

  err_putdisk:

       put_disk(md->disk);

  err_kfree:

       kfree(md);

  out:

       return ERR_PTR(ret);

}

看到这个函数的代码,我们自然就回忆起了块设备驱动的整个套路了:

1.       分配、初始化请求队列,并绑定请求队列和请求函数。

2.       分配,初始化 gendisk ,给 gendisk 的 major , fops , queue 等成员赋值,最后添加 gendisk 。

3.       注册块设备驱动。

我们看看 MMC 卡驱动程序有没有按这个套路走,

1 、 mmc_init_queue 初始了队列,并将 mmc_blk_issue_rq; 函数绑定成请求函数;

2 、 alloc_disk 分配了 gendisk 结构,并初始化了 major , fops ,和 queue ;

3 、最后调用 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 = 1, disable_multi = 0;

 

       do {

 

              mmc_wait_for_req(card->host, &brq.mrq);

              /*

                * A block was successfully transferred.

                */

              spin_lock_irq(&md->lock);

              ret = __blk_end_request(req, 0, brq.data.bytes_xfered);

              spin_unlock_irq(&md->lock);

       } while (ret);

 

       return 1;

}

这个函数实在太长了,好在我们不用全部看,大部分读数据的准备代码和出错处理的代码已经被我删掉了,只要知道读数据都是在这里完成的就够了。看不懂这个函数的,拿上 LDD3 找个人少的地方,将 sbull 研究透了也就明白这个函数了。不过这个函数里涉及的东西还挺不少,“散列表”,“回弹”都在这里出现了,有时间慢慢去研究吧。

       在块设备驱动当中你只需要抓住请求队列和请求函数就可以了,具体那些 block_device_operations 里面赋值的函数可不像字符设备驱动里面那么受关注了。

 

       分析到这里, MMC/SD 卡的驱动整个构架基本也就很明析了,说简单了就是做了两件事:

1.       卡的检测;

2.       卡数据的读取。

最后再将这两个过程大概串一下:

1.       卡的检测:

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

2.       读写数据:

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 卡的驱动分析完了,是不是有些复杂,不过这样设计的目的是为了分层,让具体平台的驱动编写更加省事。

 

http://blog.csdn.net/mmmmpl/article/details/5900760

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/130158.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 小数乘法计算题100道_leetcode题库c语言

    小数乘法计算题100道_leetcode题库c语言LeetCode算法题-Binary Tree Level Order Traversal II(Java实现)

  • [奶奶看了都会]京东自动签到薅羊毛-完整教程

    [奶奶看了都会]京东自动签到薅羊毛-完整教程又到了节假日的时间了,每逢节假日必须得搞事情。最近北京疫情管的比较严,楼主去小区旁边的小公园散步,都要出示核酸证明了。。。上一次说到用脚本完成京东自动签到领京豆:[奶奶看了都会]教你用脚本薅京东签到羊毛这个只能领到自动签到任务的豆子而已,还有好多京豆任务都没做了,导致咱白白损失了一波豆豆?所以今天嘛,我们就把京豆的任务都做一遍,把京豆全给领了?手机抓包为了获取到京豆签到的接口,需要在手机京东APP上抓包,这就需要用到手机抓包的技术了楼主对着网上的教程实践了一波,搞了一整天之后,得到的结论是An

  • cmd ping命令大全_ping命令怎么使用

    cmd ping命令大全_ping命令怎么使用简介:ping是一种Computernetworktools(电脑网络工具),作用是测试数据包是否能通过IP协议到达特定主机。Ping是Windows系统、Unix系统和Linux系统下的一个命令。它也属于一个通信协议,是TCP/IP协议的一部分。利用“ping”命令可以检查网络是否连通,可以很好地帮助我们分析和判定网络故障。该命令还可以加许多参数使用。例如:-t,-l,-n。(注意,所有ping指令都必须在知道IP的情况下使用)使用方法:主要的用法:1.-t:不断向目标IP发送数.

  • 常见的目标检测算法介绍[通俗易懂]

    常见的目标检测算法介绍[通俗易懂]2018-12-0521:12:15一、滑动窗口目标检测首先通过卷积神经网络训练一个分类器,然后使用不同尺度的窗口去裁剪输入图片进行分类。我们期望的结果是通过不同的窗口可以将需要检测的物体完全覆

  • pytest 执行用例_测试用例执行结果有哪些

    pytest 执行用例_测试用例执行结果有哪些前言平常我们功能测试用例非常多时,比如有1千条用例,假设每个用例执行需要1分钟,如果单个测试人员执行需要1000分钟才能跑完当项目非常紧急时,会需要协调多个测试资源来把任务分成两部分,于是执行时间

  • python海龟绘图画圆_Python启蒙之海龟作图「建议收藏」

    python海龟绘图画圆_Python启蒙之海龟作图「建议收藏」今天我要向大家介绍一下如何使用Python进行绘图,学会了基本绘图后,你就可以使用电脑绘制出很多漂亮的图形了,先给大家展示几幅使用Python绘图完成的精美图案吧。这副图形电脑是如何绘制出来的呢?试想一下,如果现在给你一张纸和一支笔,你如何做出这幅图形。你可以从中心点开始,然后一条条线开始绘制,直到完成最边缘的线条。电脑作图的方式就是充分模拟了你手工绘画的流程,通过程序控制了手工的作图。那既…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号