Linux mmc驱动框架

  • 卡检测函数
  • SD卡初始化
  • MMC卡初始化
  • SDIO卡初始化
  • 热插拔
    • 内核自带GPIO热插拔设备树配置
  • 总结

  上文讲到 mmc_add_host函数执行到最后会激活卡检测的延迟工作队列。延迟工作队列的执行函数为 mmc_rescan,即卡检测函数

卡检测函数

mmc_rescan

void mmc_rescan(struct work_struct *work)
{struct mmc_host *host =container_of(work, struct mmc_host, detect.work);int i;......mmc_claim_host(host);......for (i = 0; i < ARRAY_SIZE(freqs); i++) {if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))break;if (freqs[i] <= host->f_min)break;}mmc_release_host(host);out:if (host->caps & MMC_CAP_NEEDS_POLL)mmc_schedule_delayed_work(&host->detect, HZ);
}

  mmc_claim_hostmmc_release_host是成对使用的,mmc_claim_host检测当前mmc控制器是否被占用,当前mmc控制器如果被占用,那么 host->claimed = 1;否则为0,如果为1,那么会在for(;;)循环中调用schedule切换出自己,当占用mmc控制器的操作完成之后,执行 mmc_release_host()的时候,会激活登记到等待队列&host->wq中的其他程序获得mmc主控制器的物理使用权。
  最后的out部分代码,则是当需要轮训判断卡是否插入时,在本次判断完之后要将延迟工作队列重新激活。
  mmc_rescan最重要的函数是mmc_rescan_try_freq,下面来分析一下代码,函数顾名思义,就是用不同的clock去尝试初始化与目标卡的连接。
mmc_rescan->mmc_rescan_try_freq

//传递的时钟频率,依次为400K,300K,200K,100K;
static const unsigned freqs[] = { 400000, 300000, 200000, 100000 };static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{host->f_init = freq;pr_debug("%s: %s: trying to init card at %u Hz\n",mmc_hostname(host), __func__, host->f_init);mmc_power_up(host, host->ocr_avail);             //host上电/** Some eMMCs (with VCCQ always on) may not be reset after power up, so* do a hardware reset if possible.*/mmc_hw_reset_for_init(host);                        //硬件复位host,可选择性实现/** sdio_reset sends CMD52 to reset card.  Since we do not know* if the card is being re-initialized, just send it.  CMD52* should be ignored by SD/eMMC cards.* Skip it if we already know that we do not support SDIO commands*/if (!(host->caps2 & MMC_CAP2_NO_SDIO))             //发送复位sdio设备的命令(CMD52)sdio_reset(host);                               //如果提前知道不是sdio卡,则跳过该步骤mmc_go_idle(host);                                 //发送CMD0 复位SD卡。if (!(host->caps2 & MMC_CAP2_NO_SD))              //如果提前知道不是SD卡,则跳过该步骤mmc_send_if_cond(host, host->ocr_avail);
/*为了支持sd version 2.0以上的sd卡,在初始化的过程中必须在发送ACMD41之前,先发送CMD8,CMD8一般是用于
检测SD卡是否能运行在host提供的电压范围内。大家可能发现,这个调用过程没有检查是否出错,其实一般CMD8是
用来辨别目标卡是否是高容量SD卡,如果是,CMD8 会有R7应答,R7应答中会有目标SD卡支持的电压范围以及CMD8
中发送过去的“check pattern(一般是0xAA)”,否则,目标卡不会应答,在Linux 内核代码中,判断是这样的,如
果应答,目标卡就是SD高容量卡,否则出现应答超时错误,就是标准SD卡!这里的调用,主要作用是为了在发送
ACMD41之前发送CMD8,这是version 2.0及以上的规定顺序,后面还会有发送CMD8的地方,那里才是真正检测目标
卡的类型的地方。 *//* Order's important: probe SDIO, then SD, then MMC */if (!(host->caps2 & MMC_CAP2_NO_SDIO))             //如果提前知道不是SDIO卡,跳过该步骤if (!mmc_attach_sdio(host))                     //先判断是否是sdio卡return 0;if (!(host->caps2 & MMC_CAP2_NO_SD))               //如果提前知道不是SD卡,跳过该步骤if (!mmc_attach_sd(host))                     //再判读是否是SD卡return 0;if (!(host->caps2 & MMC_CAP2_NO_MMC))                //如果提前知道不是MMC卡,跳过该步骤if (!mmc_attach_mmc(host))                       //最后再判读是否是MMC卡return 0;mmc_power_off(host);                             //host掉电。return -EIO;
}

  mmc_rescan_try_freq函数代码解释在注释中基本都是写的比较明白了,中文注释是写的,英文注释是内核本身的代码。下面来依次看下mmc_attach_sdmmc_attach_mmc以及mmc_attach_sdio,函数。

SD卡初始化

mmc_rescan->mmc_rescan_try_freq->mmc_attach_sd

int mmc_attach_sd(struct mmc_host *host)
{int err;u32 ocr, rocr;WARN_ON(!host->claimed);err = mmc_send_app_op_cond(host, 0, &ocr);           /* 发送CMD41,sd设备会响应 */if (err)return err;mmc_attach_bus(host, &mmc_sd_ops);                   /* 将sd总线操作函数集分配给host */if (host->ocr_avail_sd)host->ocr_avail = host->ocr_avail_sd;           /* 设置sd的ocr *//** We need to get OCR a different way for SPI.*/if (mmc_host_is_spi(host)) {mmc_go_idle(host);err = mmc_spi_read_ocr(host, 0, &ocr);if (err)goto err;}rocr = mmc_select_voltage(host, ocr);                /* 选择合适的电压值 */......err = mmc_sd_init_card(host, rocr, NULL);          /* 检测并初始化sd card */if (err)goto err;mmc_release_host(host);err = mmc_add_card(host->card);                      /* 注册sd card */......
}

  检测到sd卡之后,比较重要的操作为mmc_sd_init_cardmmc_add_card。下面先来看下mmc_sd_init_card函数。
mmc_rescan->mmc_rescan_try_freq->mmc_attach_sdio-> mmc_sd_init_card

static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,struct mmc_card *oldcard)
{struct mmc_card *card;int err;u32 cid[4];u32 rocr = 0;WARN_ON(!host->claimed);err = mmc_sd_get_cid(host, ocr, cid, &rocr);                /* 获取CID */if (err)return err;card = mmc_alloc_card(host, &sd_type);                       /* 分配card内存,并初始化部分数据*/card->ocr = ocr;card->type = MMC_TYPE_SD;                                  /* 设置card type为sd卡 */memcpy(card->raw_cid, cid, sizeof(card->raw_cid));if (host->ops->init_card)host->ops->init_card(host, card);                     /* 如果控制器实现了init card回调,则执行 */......host->card = card;                                            /* host->card指针指向刚初始化完的card数据结构 */return 0;
}

  这个函数原函数很长,将与硬件操作相关的全部删掉,最后对我们有用的也就这几行了 mmc_alloc_card 申请了一个 struct mmc_card 结构,然后给 card->type 赋上 MMC_TYPE_SDI,最后将 card 又赋给了 host->card ,这和具体硬件还是挺像的,因为一个主控制器一般就插一个卡,有卡时 host->card 有值,没有卡时 host->card 自己就是 NULL 了。
  再来看一下比较重要的mmc_alloc_card函数:
mmc_rescan->mmc_rescan_try_freq->mmc_attach_sdio-> mmc_sd_init_card->mmc_alloc_card

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

  代码比较简单。申请内存,随后初始化card->dev,并设置partent等参数,这里bus设置为mmc_bus_type,type设置为sd_type

struct device_type sd_type = {.groups = sd_std_groups,
};

  mmc总线和i2c总线以及platform总线一样,就是我们比较熟悉的设备驱动模型这一套了。
  再回到mmc_attach_sd,看完mmc_sd_init_card后继续看mmc_add_card,这个函数再sdio、mmc卡初始化时也会调用,这里介绍完后面就不介绍了:
mmc_rescan->mmc_rescan_try_freq->mmc_attach_sd-> mmc_add_card

int mmc_add_card(struct mmc_card *card)
{int ret;const char *type;......dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);        /* 设置设备名称 */switch (card->type) {case MMC_TYPE_MMC:type = "MMC";break;case MMC_TYPE_SD:type = "SD";if (mmc_card_blockaddr(card)) {if (mmc_card_ext_capacity(card))type = "SDXC";elsetype = "SDHC";}break;case MMC_TYPE_SDIO:type = "SDIO";break;case MMC_TYPE_SD_COMBO:type = "SD-combo";if (mmc_card_blockaddr(card))type = "SDHC-combo";break;default:type = "?";break;}......if (mmc_host_is_spi(card->host)) {pr_info("%s: new %s%s%s card on SPI\n",mmc_hostname(card->host),mmc_card_hs(card) ? "high speed " : "",mmc_card_ddr52(card) ? "DDR " : "",type);} else {pr_info("%s: new %s%s%s%s%s%s card at address %04x\n",mmc_hostname(card->host),mmc_card_uhs(card) ? "ultra high speed " :(mmc_card_hs(card) ? "high speed " : ""),mmc_card_hs400(card) ? "HS400 " :(mmc_card_hs200(card) ? "HS200 " : ""),mmc_card_hs400es(card) ? "Enhanced strobe " : "",mmc_card_ddr52(card) ? "DDR " : "",uhs_bus_speed_mode, type, card->rca);}......ret = device_add(&card->dev);if (ret)return ret;mmc_card_set_present(card);return 0;
}

  这个函数主要作用一是,打印card信息,上面一大段代码都是为了最终打印做准备的。
不同类型卡打印也不一样,我手上设备刚好有mmc、sd卡接口以及sdio wifi模组,下面看一下上电打印:

mmc4: new DDR MMC card at address 0001                               /* mmc card */
mmc2: new high speed SDHC card at address 59b4                      /* sd card */
mmc3: new high speed SDIO card at address 0001                      /* sdio card(sdio wifi) */

  添加设备到设备驱动模型中,line6设置了设备名称,最后调用device_add。启动后可以通过sys文件系统查看。上文介绍了,bus设置为mmc_bus_type,则在/sys/bus/mmc/devices下查看设备我手上设备上电后查看mmc bus下的设备,如下:

[root@linux:/root]# ls /sys/bus/mmc/devices/
mmc2:59b4  mmc3:0001  mmc4:0001

  既然设备添加到了mmc bus总线上,那么必然要有驱动。首先来看下mmc bus的数据结构:
mmc_bus_type

static struct bus_type mmc_bus_type = {.name        = "mmc",.dev_groups  = mmc_dev_groups,.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,
};

  在 device_add 里面,设备对应的总线会拿着你这个设备和挂在这个总线上的所有驱动程序去匹配( match ),此时会调用总线的 match 函数,如果匹配到了就会调用总线的 probe 函数或驱动的 probe 函数,那我们看一下这里的 mmc_bus_match 是如何进行匹配的:
mmc_bus_type ->mmc_bus_match

static int mmc_bus_match(struct device *dev, struct device_driver *drv)
{return 1;
}

  根据代码来看 match 永远都能成功,那就去执行 probe 吧:
mmc_bus_type ->mmc_bus_probe

static int mmc_bus_probe(struct device *dev)
{struct mmc_driver *drv = to_mmc_driver(dev->driver);struct mmc_card *card = mmc_dev_to_card(dev);return drv->probe(card);
}

  在这个函数里面又调用了一下 drv->probe() ,那这个drv是什么呢?上面有:struct mmc_driver *drv = to_mmc_driver(dev->driver);match 函数总是返回 1 ,那看来只要是挂在这条总线上的 driver 都有可能跑到这里来了,事实的确也是这样的,不过好在挂在这条总线上的 driver 只有一个,它是这样定义的:
struct mmc_driver mmc_driver

static struct mmc_driver mmc_driver = {.drv     = {.name   = "mmcblk",.pm   = &mmc_blk_pm_ops,},.probe     = mmc_blk_probe,.remove        = mmc_blk_remove,.shutdown = mmc_blk_shutdown,
};

  看到这里时, mmc子系统的card/core/host 几个已经全部被扯进来了,边看 mmc_driver 中的几个函数,就能明白其中的联系了,那我们继续看:
mmc_driver->probe

static int mmc_blk_probe(struct mmc_card *card)
{struct mmc_blk_data *md, *part_md;......md = mmc_blk_alloc(card);                         /* alloc_disk 和初始化队列 */......if (mmc_add_disk(md))                              /* 注册块设备 */goto out;list_for_each_entry(part_md, &md->part, part) {if (mmc_add_disk(part_md))goto out;}......return 0;
}

  mmc_blk_alloc中主要包含了alloc_disk 和初始化队列,最后调用了 add_disk ,到这里就全部都是块设备的注册操作了。最终,mmc bus上的driver驱动初始化后,注册块设备成功后,我手上设备的mmc卡打印如下:

[    2.198146] mmcblk4: mmc4:0001 8WPD3R 7.28 GiB
[    2.202910] mmcblk4boot0: mmc4:0001 8WPD3R partition 1 4.00 MiB
[    2.216759] mmcblk4boot1: mmc4:0001 8WPD3R partition 2 4.00 MiB
[    2.229342]  mmcblk4: p1(uboot) p2(firmware) p3(config) p4(others)

  在文件系统下通过设备节点查看,打印如下,这是我的mmc卡:

mmcblk4
mmcblk4boot0
mmcblk4boot1
mmcblk4p1
mmcblk4p2
mmcblk4p3
mmcblk4p4

  块设备驱动(mmc_driver)具体代码我这里就不展开说明了。

MMC卡初始化

mmc_rescan->mmc_rescan_try_freq->mmc_attach_mmc

int mmc_attach_mmc(struct mmc_host *host)
{int err;u32 ocr, rocr;WARN_ON(!host->claimed);/* Set correct bus mode for MMC before attempting attach */if (!mmc_host_is_spi(host))mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN);  /* 设置正确的总线模式在检测卡之前 */err = mmc_send_op_cond(host, 0, &ocr);                /* 发送CMD1指令,SEND_OP_COND,mmc设备会相应 */if (err)return err;mmc_attach_bus(host, &mmc_ops);                        /* 将mmc总线操作函数集分配给host */if (host->ocr_avail_mmc)host->ocr_avail = host->ocr_avail_mmc;            /* 设置mmc的ocr *//** We need to get OCR a different way for SPI.*/if (mmc_host_is_spi(host)) {err = mmc_spi_read_ocr(host, 1, &ocr);if (err)goto err;}rocr = mmc_select_voltage(host, ocr);             /* 选择合适的电压值 */......err = mmc_init_card(host, rocr, NULL);             /* 识别和初始化mmc card */if (err)goto err;mmc_release_host(host);err = mmc_add_card(host->card);                     /* 注册mmc card */if (err)goto remove_card;mmc_claim_host(host);return 0;remove_card:mmc_remove_card(host->card);mmc_claim_host(host);host->card = NULL;
err:mmc_detach_bus(host);pr_err("%s: error %d whilst initialising MMC card\n",mmc_hostname(host), err);return err;
}

  检测到mmc卡之后,比较重要的操作为mmc_init_cardmmc_add_card。mmc卡总的流程基本和sd卡一致,只是涉及到卡的命令等不一样,但是最终都是注册为块设备。mmc_add_card在上文介绍SD卡初始化的时候已经讲过了。这里只要看一下mmc_init_card就可以了:
mmc_rescan->mmc_rescan_try_freq->mmc_attach_mmc-> mmc_init_card

static int mmc_init_card(struct mmc_host *host, u32 ocr,struct mmc_card *oldcard)
{struct mmc_card *card;card = mmc_alloc_card(host, &mmc_type);card->ocr = ocr;card->type = MMC_TYPE_MMC;card->rca = 1;memcpy(card->raw_cid, cid, sizeof(card->raw_cid));/** Call the optional HC's init_card function to handle quirks.*/if (host->ops->init_card)host->ops->init_card(host, card);......if (!oldcard)host->card = card;return 0;
}

  这个函数原函数y也很长,将与硬件操作相关的全部删掉,最后对我们有用的也就这几行了 mmc_alloc_card 申请了一个 struct mmc_card 结构,值得注意的是这里mmc_alloc_card传参type设置为mmc_type

static struct device_type mmc_type = {.groups = mmc_std_groups,
};

  然后给 card->type 赋上 MMC_TYPE_MMC,最后将 card 又赋给了 host->card ,可以看出,和mmc_sd_init_card基本上如出一辙。其余的操作参考上文sd卡检测流程。
  add card中都是注册mmc bus类型的device,并最终匹配mmc_driver,注册块设备;只不过涉及到卡特性,寄存器操作等有差异。

SDIO卡初始化

mmc_rescan->mmc_rescan_try_freq->mmc_attach_sdio

int mmc_attach_sdio(struct mmc_host *host)
{int err, i, funcs;u32 ocr, rocr;struct mmc_card *card;WARN_ON(!host->claimed);err = mmc_send_io_op_cond(host, 0, &ocr);            /* 发送CMD5,sdio设备会响应 */if (err)return err;mmc_attach_bus(host, &mmc_sdio_ops);                /* 将sdio总线操作函数集分配给host */if (host->ocr_avail_sdio)host->ocr_avail = host->ocr_avail_sdio;         /* 设置sdio的ocr */rocr = mmc_select_voltage(host, ocr);              /* 选择合适的电压值 */......err = mmc_sdio_init_card(host, rocr, NULL, 0);     /* 检测并初始化sdio card */if (err)goto err;card = host->card;......funcs = (ocr & 0x70000000) >> 28;                  /* 卡支持的sdio function 数量在ocr中包含了 */card->sdio_funcs = 0;for (i = 0; i < funcs; i++, card->sdio_funcs++) {err = sdio_init_func(host->card, i + 1);        /* 初始化所有sdio function ,但是不添加 */if (err)goto remove;......}/** First add the card to the driver model...*/mmc_release_host(host);err = mmc_add_card(host->card);                      /* 注册sdio card */if (err)goto remove_added;/** ...then the SDIO functions.*/for (i = 0;i < funcs;i++) {err = sdio_add_func(host->card->sdio_func[i]);  /* 注册所有sdio function */if (err)goto remove_added;}......
}

  检测到sdio卡之后,比较重要的操作为mmc_sdio_init_cardmmc_add_card以及sdio_init_funcsdio_add_funcmmc_add_card上文中已经介绍过了,这里就不展开了。
mmc_rescan->mmc_rescan_try_freq->mmc_attach_sdio-> mmc_sdio_init_card

static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr,struct mmc_card *oldcard, int powered_resume)
{struct mmc_card *card;......card = mmc_alloc_card(host, NULL);                                /* 分配card内存,并初始化部分数据 */if (IS_ERR(card)) {err = PTR_ERR(card);goto err;}if ((rocr & R4_MEMORY_PRESENT) &&mmc_sd_get_cid(host, ocr & rocr, card->raw_cid, NULL) == 0) {card->type = MMC_TYPE_SD_COMBO;if (oldcard && (oldcard->type != MMC_TYPE_SD_COMBO ||memcmp(card->raw_cid, oldcard->raw_cid, sizeof(card->raw_cid)) != 0)) {mmc_remove_card(card);return -ENOENT;}} else {card->type = MMC_TYPE_SDIO;if (oldcard && oldcard->type != MMC_TYPE_SDIO) {mmc_remove_card(card);return -ENOENT;}}if (host->ops->init_card)host->ops->init_card(host, card);                          /* 如果控制器驱动实现了对应回调,则执行 */......
finish:if (!oldcard)host->card = card;return 0;
}

  这个函数原函数很长,将与硬件操作相关的全部删掉,最后对我们有用的也就这几行了 mmc_alloc_card 申请了一个 struct mmc_card 结构,然后给 card->type 赋上 MMC_TYPE_SDIO ,最后将 card 又赋给了 host->card ,这和具体硬件还是挺像的,因为一个主控制器一般就插一个卡,有卡时 host->card 有值,没有卡时 host->card 自己就是 NULL 了。
  再回到mmc_attach_sdio,会调用mmc_add_card到mmc bus下。最终在/sys/bus/mmc/devices/下生成节点。
  最后看一下sdio_init_funcsdio_add_func,前面有介绍过struct sdio_func时说过一张sdio卡可以支持很多Functions,一个Function对应软件上的struct sdio_func结构体,最后反应到驱动模型上就是一个device。
mmc_rescan->mmc_rescan_try_freq->mmc_attach_sdio-> sdio_init_func

static int sdio_init_func(struct mmc_card *card, unsigned int fn)
{int ret;struct sdio_func *func;if (WARN_ON(fn > SDIO_MAX_FUNCS))return -EINVAL;func = sdio_alloc_func(card);                           /* 申请内存 */func->num = fn;if (!(card->quirks & MMC_QUIRK_NONSTD_SDIO)) {          /* 判断是否是标准sdio卡 */ret = sdio_read_fbr(func);                           /* 标准卡根据fn号,去读取func的vendor id、device id以及max_blksize*/if (ret)goto fail;ret = sdio_read_func_cis(func);if (ret)goto fail;} else {func->vendor = func->card->cis.vendor;               /* 非标准卡直接从card->cis中赋值vendor id、device id以及max_blksize */func->device = func->card->cis.device;func->max_blksize = func->card->cis.blksize;}card->sdio_func[fn - 1] = func;                          /* 将初始化好的sdio_func指针赋值给func数组 */return 0;fail:/** It is okay to remove the function here even though we hold* the host lock as we haven't registered the device yet.*/sdio_remove_func(func);return ret;
}

  sdio_alloc_func类似于上文中介绍的mmc_alloc_card,这里有必要展开说明一下,代码如下:
mmc_rescan->mmc_rescan_try_freq->mmc_attach_sdio-> sdio_init_func->sdio_alloc_func

struct sdio_func *sdio_alloc_func(struct mmc_card *card)
{struct sdio_func *func;func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL);......func->card = card;......device_initialize(&func->dev);func->dev.parent = &card->dev;func->dev.bus = &sdio_bus_type;func->dev.release = sdio_release_func;return func;
}

  代码与mmc_alloc_card如出一辙。申请内存,随后初始化card->dev,并设置partent等参数,与mmc_alloc_card不同的是,这里bus设置为sdio_bus_type。
  看完sdio_init_func,再来看一下sdio_add_func
mmc_rescan->mmc_rescan_try_freq->mmc_attach_sdio-> sdio_add_func

int sdio_add_func(struct sdio_func *func)
{int ret;dev_set_name(&func->dev, "%s:%d", mmc_card_id(func->card), func->num);      /* 设置设备名称 */sdio_set_of_node(func);sdio_acpi_set_handle(func);device_enable_async_suspend(&func->dev);ret = device_add(&func->dev);                                              /* 添加到设备驱动模型 */if (ret == 0)sdio_func_set_present(func);return ret;
}

  这个函数也和mmc_add_card如出一辙,设置sdio func设备名称,并添加到设备驱动模型。启动后同样可以通过sys文件系统查看。上文介绍了,bus设置为sdio_bus_type,则在/sys/bus/sdio/devices下查看设备我手上设备上电后查看sdio bus下的设备,这里是一个sdio wifi模组,如下:

[root@linux:/root]# ls /sys/bus/sdio/devices/
mmc3:0001:1                                                             /* 最后的1代表func num */

  跟上文的mmc_bus类似,设备添加到了sdio bus总线上,那么必然要有驱动。首先来看下sdio bus的数据结构:
sdio_bus_type

static struct bus_type sdio_bus_type = {.name       = "sdio",.dev_groups = sdio_dev_groups,.match       = sdio_bus_match,.uevent       = sdio_bus_uevent,.probe       = sdio_bus_probe,.remove       = sdio_bus_remove,.pm      = &sdio_bus_pm_ops,
};

  在 device_add 里面,设备对应的总线会拿着你这个设备和挂在这个总线上的所有驱动程序去匹配( match ),此时会调用总线的 match 函数,如果匹配到了就会调用总线的 probe 函数或驱动的 probe 函数,那我们看一下这里的 sdio_bus_match 是如何进行匹配的:
sdio_bus_type ->sdio_bus_match

static int sdio_bus_match(struct device *dev, struct device_driver *drv)
{struct sdio_func *func = dev_to_sdio_func(dev);struct sdio_driver *sdrv = to_sdio_driver(drv);if (sdio_match_device(func, sdrv))return 1;return 0;
}static const struct sdio_device_id *sdio_match_device(struct sdio_func *func,struct sdio_driver *sdrv)
{const struct sdio_device_id *ids;ids = sdrv->id_table;                                     /* 驱动支持的id表 */if (ids) {while (ids->class || ids->vendor || ids->device) {if (sdio_match_one(func, ids))                       /* driver中的id去匹配device id */return ids;                                     /* 匹配上返回id值(成功) */ids++;}}return NULL;
}static const struct sdio_device_id *sdio_match_one(struct sdio_func *func,const struct sdio_device_id *id)
{if (id->class != (__u8)SDIO_ANY_ID && id->class != func->class)return NULL;if (id->vendor != (__u16)SDIO_ANY_ID && id->vendor != func->vendor)return NULL;if (id->device != (__u16)SDIO_ANY_ID && id->device != func->device)return NULL;return id;
}

  简单的来说。是通过对比dirver支持的id table和device中从设备中读出来的id,match上就去执行probe吧。
sdio_bus_type ->sdio_bus_probe

static int sdio_bus_probe(struct device *dev)
{struct sdio_driver *drv = to_sdio_driver(dev->driver);struct sdio_func *func = dev_to_sdio_func(dev);const struct sdio_device_id *id;int ret;id = sdio_match_device(func, drv);if (!id)return -ENODEV;ret = dev_pm_domain_attach(dev, false);if (ret == -EPROBE_DEFER)return ret;/* Unbound SDIO functions are always suspended.* During probe, the function is set active and the usage count* is incremented.  If the driver supports runtime PM,* it should call pm_runtime_put_noidle() in its probe routine and* pm_runtime_get_noresume() in its remove routine.*/if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD) {ret = pm_runtime_get_sync(dev);if (ret < 0)goto disable_runtimepm;}/* Set the default block size so the driver is sure it's something* sensible. */sdio_claim_host(func);ret = sdio_set_block_size(func, 0);sdio_release_host(func);if (ret)goto disable_runtimepm;ret = drv->probe(func, id);                               /* 调用驱动的probe函数 */if (ret)goto disable_runtimepm;return 0;disable_runtimepm:if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)pm_runtime_put_noidle(dev);dev_pm_domain_detach(dev, false);return ret;
}

  中间那些代码不需要过多的关注,最终调用drv->probe(func, id);这里举例一个sdio driver数据结构:

static struct sdio_driver sdio_driver = {.name      = "cw1200_wlan_sdio",.id_table   = cw1200_sdio_ids,.probe       = cw1200_sdio_probe,.remove        = cw1200_sdio_disconnect,
#ifdef CONFIG_PM.drv = {.pm = &cw1200_pm_ops,}
#endif
};

  进行到这一步,sdio driver的probe函数被调用,则进入sdio驱动的初始化流程了。我们熟知的rtl8189驱动,probe函数就在这时候被调用了。
  wifi驱动,只编译过原厂给的驱动,没有仔细看过驱动,就不班门弄斧了~

热插拔

  前面讲到控制器驱动的probe 函数做的事情,准备一个 mmc_host 结构,然后添加一个主控制器设备到内核,最后又调用了一下 mmc_rescan 来检测是不是有卡插入了。
  如果上电的时候有卡插着还好,可以去操作卡了,那如果没有卡插入呢? mmc_rescan 不是白调用了一次吗?
  可是卡插入时为什么 PC 还是能检测到呢?看来卡检测的动作不光是在 probe 的最后一步做了一次,其它地方也有做。

内核自带GPIO热插拔设备树配置

broken-cd 表示没有热插拔探测引脚,使用轮询检测
cd-gpios 使用gpio管脚作为热插拔探测引脚
non-removable 表示不能进行热插拔,设备一直连接(比如eMMC)
上面三个选项用于指定热插拔探测选项,如果三个选项都没有指定,则使用主机自带的热插拔引脚sdcd,有些控制器通过轮训查询热插拔。
cd-inverted 表示cd引脚是active high

总结

  把流程大概过一下:

/* 通用部分 */
sunxi_mmc_probe(host/sumxi-mmc.c)->mmc_alloc_host(core/core.c)->mmc_rescan(core/core.c)->mmc_rescan_try_freq(core/core.c)->mmc_attach_sdio(core/sdio.c)mmc_attach_sd(core/sd.c)mmc_attach_mmc(core/mmc.c)
/* sd卡 */
mmc_attach_sd(core/sd.c)->mmc_sd_init_card(core/sd.c)->mmc_alloc_card(core/bus.c)mmc_add_card(core/bus.c)->device_addmmc_bus_match(core/bus.c)->mmc_bus_probe(core/bus.c)->mmc_blk_probe(card/block.c)->alloc_disk/add_disk/* mmc卡 */
mmc_attach_mmc(core/mmc.c)->mmc_init_card(core/mmc.c)->mmc_alloc_card(core/bus.c)mmc_add_card(core/bus.c)->device_addmmc_bus_match(core/bus.c)->mmc_bus_probe(core/bus.c)->mmc_blk_probe(card/block.c)->alloc_disk/add_disk/* sdio卡 */
mmc_attach_sdio(core/sdio.c)->mmc_sdio_init_card(core/sdio.c)->mmc_alloc_card(core/bus.c)mmc_add_card(core/bus.c)->sdio_init_func(core/sdio.c)->sdio_add_func(core/sdio_bus.c)->device_addsdio_bus_match(core/sdio_bus.c)->sdio_bus_probe(core/sdio_bus.c)->sdio_driver->probe

  SDIO/MMC/SD 卡的驱动流程就此分析完毕。

Linux mmc驱动框架(4)——卡检测及初始化相关推荐

  1. linux mmc驱动框架,Linux mmc framework2:基本组件之mmc

    1.前言 本文主要mmc组件的主要流程,在介绍的过程中,将详细说明和mmc相关的流程,涉及到其它组件的详细流程再在相关文章中说明. 2.主要数据结构和API TODO 3. 主要流程 3.1 mmc_ ...

  2. mmc驱动框架基础介绍

    mmc驱动框架基础介绍 本文主要介绍一下Linux内核的mmc子系统驱动的整体框架. (作者对SDIO设备不熟悉,所以不过多描述:鄙人才疏学浅,有不当之处,还请指教.) 大概包括以下几个部分: mmc ...

  3. Linux MMC 驱动子系统详解

    Linxu MMC 驱动子系统 文章目录 Linxu MMC 驱动子系统 硬件关联 目录说明 mmc子系统的逻辑架构 设备-总线-驱动模型 一.MMC驱动抽象模型 二.SDIO驱动抽象模型 三.MMC ...

  4. Linux MMC驱动架构浅析

    Linux MMC驱动架构浅析 MMC驱动模型 Linux内核设计了MMC子系统,用于管理MMC/SD等设备,MMC/SD存储设备是一种典型的块设备.MMC子系统的框架结构如下图所示. 块设备(MMC ...

  5. Linux PCI驱动框架分析:(Peripheral Component Interconnect,外部设备互联)

    <DPDK 20.05 | rte_pci_bus思维导图 | 第一版> <linux系统下:IO端口,内存,PCI总线 的 读写(I/O)操作> <Linux指令:ls ...

  6. Linux USB驱动框架分析 【转】

    转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...

  7. Linux PCIe驱动框架分析(第二章)

    目录 项目背景 1. 概述 2. 数据结构 3. 流程分析 3.1 设备驱动模型 3.2 初始化 3.2.1 pci_bus_match 3.2.2 pci_device_probe 3.3 枚举 项 ...

  8. OS相关驱动 Linux USB驱动框架分析

    初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结当然不可缺,更何况我决定为嵌入式卖命了.好,言归正传,我说一说这段时间的收获,跟大家分享一下Linux的驱动开发 ...

  9. 深入分析Linux PCI驱动框架分析(二)

    说明: Kernel版本:4.14 ARM64处理器 使用工具:Source Insight 3.5, Visio 1. 概述 本文将分析Linux PCI子系统的框架,主要围绕Linux PCI子系 ...

  10. Linux uart驱动框架

    Linux uart驱动框架 串口驱动框架包括两部分 struct uart_driver int uart_register_driver(struct uart_driver *uart); vo ...

最新文章

  1. 本地复制不能粘贴到服务上
  2. vs中列表分页符代码_电脑办公技巧Excel中Ctrl+K的使用技巧(十二)/Word2016中快速删除分页符与空白页的方法...
  3. python 必备模块和包_Python_异常和模块
  4. OpenGL创建一个GLFW背景红色窗口的实例
  5. python条件控制语句_Python课堂笔记 条件控制语句
  6. 内部收益率irr_介绍一个神器,内部收益率IRR
  7. UIProgressView-初识IOS
  8. devops 文化_谁在DevOps中推动文化发展?
  9. xml TO json(非递归实现)
  10. 构造模式(Builder Pattern)
  11. kotlin中文开发文档
  12. WebLogic下载地址(各版本)
  13. 【高级持续性威胁追踪】当黑客不讲武德,安全专家也容易被骗
  14. 计算机英语句子及译文,英语经典句子
  15. 四阶幻方c语言编程,13年 第四届 蓝桥杯C语言C组 第4题 幻方填空
  16. 用python的opencv画出一棵树的骨架
  17. android中怎么播放本地视频播放器,安卓之播放本地视频讲解
  18. reverse()的使用
  19. 涨停缩量平台调整选股策略(附筛选python代码)
  20. HDLbits 4位BCD计数器2

热门文章

  1. 免费又好用怎么把文字转换成语音呢?分享我常用的3个配音神器
  2. Detours内联HOOK
  3. 吉林大学 校园网 认证相关 (SUSPEND)
  4. 颜色基础知识——CIE 1931色度坐标
  5. 参考文献标号字体_参考文献标号字体 参考文献标准格式字体
  6. visio流程图怎么合并线_6步轻松做Visio跨职能流程图
  7. linux 3g拨号,中兴MF637U 3G 联通 linux 拨号
  8. 5G关键技术之D2D通信技术
  9. python去重脚本
  10. 电容或电感的电压_Buck知识大总结:模态分析,电感计算,EMI分析等(转)