`

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.hMMC卡来分析,SD卡驱动程序流程类似。

host/

s3cmci.c/s3cmci.hS3C24XXMMC/SD卡控制器为例,其它类型的控制器类似。

LINUX当中对目录的划分是很有讲究的,这些文件被分布在3个目录下,正好对应MMC/SD驱动程序的3个层次(关于层的划分这里浏览一下,有个概念即可,当我们分析完了后再回头来看,你会觉得很形象):

(1)区块层

主要是按照LINUX块设备驱动程序的框架实现一个卡的块设备驱动,这block.c当中我们可以看到写一个块设备驱动程序时需要的block_device_operations结构体变量的定义,其中有open/release/request函数的实现,而queue.c则是对内核提供的请求队列的封装,我们暂时不用深入理解它,只需要知道一个块设备需要一个请求队列就可以了。

(2)核心层

核心层封装了MMC/SD卡的命令,例如存储卡的识别,设置,读写。例如不管什么卡都应该有一些识别,设置,和读写的命令,这些流程都是必须要有的,只是具体对于不同的卡会有一些各自特有的操作。Core.c文件是由sd.cmmc.c两个文件支撑的,core.cMMC卡、SD卡的共性抽象出来,它们的差别由sd.csd_ops.cmmc.cmmc_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_hostmmc_add_host。函数命名已经很形象了,前者是申请一个mmc_host,而后者是添加一个mmc_host。中间还有一个操作,就是给mmcops成员赋上了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来描述的,在随后的delayjiffies后会有一个记录在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时读FLASHID一样,网卡也是这样的,不用管这个值的意义了,只要知道它能标识是一个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_addcard->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,给gendiskmajorfopsqueue等成员赋值,最后添加gendisk

3.注册块设备驱动。

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

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

2alloc_disk分配了gendisk结构,并初始化了majorfops,和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_rqcard/block.c

mmc_wait_for_req(core/core.c)

mmc_start_request(core/core.c)

host->ops->request(host, mrq)// s3cmcis3cmci_request

MMC/SD卡的驱动分析完了,是不是有些复杂,不过这样设计的目的是为了分层,让具体平台的驱动编写更加省事。

http://blog.163.com/prevBlogPerma.do?host=cupidove&srl=100566220117611505857&mode=prev



分享到:
评论

相关推荐

    MMC卡块驱动分析-和fat文件系统

    MMC卡块驱动分析-和fat文件系统

    linux SD卡驱动分析

    SD 卡驱动分析...................................................................................................................................1 块请求处理(linux/driver/mmc/card)........................

    linux sd 卡 驱动 分析 流程图

    前几天写了篇“基于S3C2410的SD卡linux驱动工作原理(一)”,说了下脱离操作...Linux中SD驱动可以分为3层:块设备层(mmc_block.c ,mmc_sysfs.c,mmc_queue.c)、mmc协议层(mmc.c)、sd驱动层(s3c2410_sdi.c)。

    基于s3c2410的SD卡驱动分析

    分析了drivers/mmc/目录下的card、core、host三层的代码,完全从注册到最后的操作底层的寄存器。相当详细!

    S5PV210裸机SD卡驱动

    S5PV210裸机开发之SD卡驱动,驱动包含一个C文件、一个头文件和测试main文件,经过测试,可以读、写...该驱动只分析4位sd模式、sd2.0及sd1.0版本的sd卡驱动实现,sd2.0以上版本sd卡、MMC卡、spi方式读写sd卡在本文不适用

    SD卡物理层规范学习套装.zip

    Part_1_Physical_Layer_Specification_Ver2.00_Final_060509.pdf【184页】 SD卡系统物理层规范(中文翻译).pdf【74页】 MMC子系统流程分析.doc linux SD卡驱动分析.pdf

    嵌入式Linux之我行系列

    ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之MMC/SD卡驱动 ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之LCD驱动 ·嵌入式Linux之我行——Linux-2.6.30.4在2440上的移植之触摸屏驱动 ·嵌入式...

    存储卡数据恢复

    它能直接恢复硬盘、闪盘、存储卡(如 SD 卡,MMC 卡等)中的文件,只要被删除文件所在磁盘没有被重复写入 数据,无论格式化还是删除均能直接恢复。支持 FAT12,FAT16,FAT32,NTFS以及最新加入支持的exFAT文件...

    ARMSYS2410套餐4

    完全支持SD/MMC卡; 全面引出的总线和I/O,供用户无局限地扩展系统。 更完整的软件配置 &lt;br&gt; 提供WINCE5.0和Linux2.6.15操作系统; 提供ADS工程文件形式提供底层硬件驱动程序; 在...

    Magic ARM 2410

    6.6 SD/MMC卡实验 ..................................230 6.7 触摸屏实验...........................................235 6.8 PCMCIA 接口CF卡实验....................239 6.9 IDE 硬盘实验.....................

    mini2440之U-boot移植详细手册-20100419

    4.2.9 SD卡(MMC) 指令 ............................................................................................................................... .......... 32  4.2.10 FAT文件系统指令 .............

Global site tag (gtag.js) - Google Analytics