core层处理(linux/driver/mmc/core)

1.     core层初始化

一切变化逃不出Kconfig/Makefile的魔爪,这话一点也不假。同样core层的故事也将从这里拉开帷幕。二话不说先还是进到core目录下瞧瞧…

与以往所见到的Kconfig相比这里的显然少了几分生机和活力,貌似整个文件看完也难以发现令我们眼前发亮的字眼。也罢,少一个config也许就意味这我们少看几千行代码。再看看Makefile:

[core/Makefile]

5    ifeq ($(CONFIG_MMC_DEBUG),y)

6           EXTRA_CFLAGS         += -DDEBUG

7    endif

8

9    obj-$(CONFIG_MMC)         += mmc_core.o

10   mmc_core-y                 := core.o bus.o host.o \

11                                  mmc.o mmc_ops.o sd.o sd_ops.o \

12                                  sdio.o sdio_ops.o sdio_bus.o \

13                                  sdio_cis.o sdio_io.o sdio_irq.o

14

15   mmc_core-$(CONFIG_DEBUG_FS)   += debugfs.o

看到这里我们再也兴奋不起来了,好像处理debugfs.c这个文件我们可以不怎么关注外,其他的文件都是我们研究的重点了。命运本该如此,不能改变就学着去接受吧.....

知道了是那些个文件再去找入口也许就方便多了,前面我们说过module_init和subsys_initcall永远是linux内核中最“忠实”的奸臣。当然也还有其他的乱臣贼子,一张口就道出内核入口的,这里就不在一一列出了。前面说到card目录的时候,入口显然是module_init,记性稍微好点的哥们可能还记得当时是因为mmc_bus_type这条总线才让我们card的故事得以延续的。换句话来说,如果这条总线都还尚未注册,那么请问card目录又将如何利用总线mmc_bus_type的probe方法最终走向mmc_driver->probe?无花却有果,那才真是奇了怪了。说来这么多无非是想证明其实core目录是早于card目录而生的,而我们又知道对于subsys_initcall是早于module_init调用的,那么也就不难想到core很有可能就是利用subsys_initcall来入口的了。

是不是这个理搜索一下内核代码就知道了,直接在/mmc/core目录下搜索subsys_initcall关键字,出来的不是别人真是core.c这个文件。不信邪的可以去搜索module_init,要是能搜索出东西来,那就是出了鬼了,说不好多半是上辈子造下的孽,这辈子该还了。好了,是时候进入正题了,先看subsys_initcall(mmc_init)如下:

[mmc/core/core.c]

1315      static int __init mmc_init(void)

1316      {

1317             int ret;

1318

1319             workqueue = create_singlethread_workqueue("kmmcd");

1320             if (!workqueue)

1321                    return -ENOMEM;

1322

1323             ret = mmc_register_bus();

1324             if (ret)

1325                    goto destroy_workqueue;

1326

1327             ret = mmc_register_host_class();

1328             if (ret)

1329                    goto unregister_bus;

1330

1331             ret = sdio_register_bus();

1332             if (ret)

1333                    goto unregister_host_class;

1334

1335             return 0;

1336

1337      unregister_host_class:

1338             mmc_unregister_host_class();

1339      unregister_bus:

1340             mmc_unregister_bus();

1341      destroy_workqueue:

1342             destroy_workqueue(workqueue);

1343

1344             return ret;

1345      }

1319行内核时间处理机制中大名鼎鼎的工作队列就被使用在这里了。我们知道每个工作队列有一个或多个专用的进程("内核线程"),它运行提交给这个队列的函数。通常我们使用create_workqueue来创建一个工作队列,实际上他可能创建了多个线程运行在系统不同的处理器上。然而在很多情况下,我们提交的任务可能是些简单的单线程就能够完成的工作,这时候使用create_singlethread_workqueue来代替创建工作队列时在适用不过了。这里就是直接使用create_singlethread_workqueue创建一个单线程的工作队列。

1323行之前分析内核入口的时候一而再再而三的提到mmc_bus_type这么一条总线,现在她终于是有机会抛头露面了。可以猜测mmc_register_bus注册的不是别人正是垂涎已久的mmc_bus_type,不信你就来看代码。

[mmc/core/bus.c]

150 int mmc_register_bus(void)

151 {

152        return bus_register(&mmc_bus_type);

153 }

看这代码清晰简单,比起小葱拌豆腐还一清二白。如果你硬是说不认识bus_register那我就没辙了,回去翻翻设备模型估计第一页就有讲述他老人家的风骚故事。

1327行这句话看不看能,如果你硬是对sys目录下的那点东西怎么来的感兴趣的话,就去瞅两眼吧。一眼就够了,太多了伤身体。

1331行又来个sdio_bus注册,本来一个mmc_bus_type就折腾的够烦人的了,现在又来个sdio_bus是个什么东东,无形中给我们增加压力。不知道是什么东西就百度百科一下吧,谁知道塞翁失马焉知非福,下面来点百度的东西。其实SDIO是目前我们比较关心的技术,SDIO故名思义,就是 SD的 I/O接口(interface)的意思,不过这样解释可能还有点抽像。更具体的说明,SD本来是记忆卡的标准,但是现在也可以把 SD 拿来插上一些外围接口使用,这样的技术便是 SDIO。所以 SDIO本身是一种相当单纯的技术,透过 SD的 I/O接脚来连接外部外围,并且透过 SD上的 I/O数据接位与这些外围传输数据,而且 SD协会会员也推出很完整的 SDIO stack驱动程序,使得 SDIO外围(我们称为 SDIO卡)的开发与应用变得相当热门。现在已经有非常多的手机或是手持装置都支持 SDIO的功能(SD标准原本就是针对 mobile device而制定),而且许多 SDIO外围也都被开发出来,让手机外接外围更加容易,并且开发上更有弹性(不需要内建外围)。目前常见的 SDIO外围(SDIO卡)有:Wi-Fi card(无线网络卡)、CMOS sensor card(照相模块)、GPS card

GSM/GPRS modem card、Bluetooth card、Radio/TV card。SDIO的应用将是未来嵌入式系统最重要的接口技术之一,并且也会取代目前 GPIO式的 SPI 接口。

看完这堆科普知识,是不是有点柳暗花明又一村的感觉,其实这儿注册个sdio_bus就是为那些sdio外设服务的,就像前面我们分析的card目录使用mmc_bus,说不准哪天又多出个什么wi-fi卡就要依附在这条sdio总线上,那时我们就可以像mmc_bus一样拿来用了。至少这里我们现在还不用管他,这也就是说刚才core中见到的若干个文件,只要名字带了个sdio的头的,我们八成都不用再来管他了。你说这是福还是祸,是福跑不了,是祸躲不过。

mmc_init比较简短,说到这里也就算是结束了。一般来说core层所做的初始化的工作较少,多半是为整个子系统的工作提供必要的接口,就像前面分析块层设备驱动一样。另外,前面我们说过在card层给我们core的分析留下了一些线索,下面我们就来按之前遗留下来的函数的顺序对其一一进行分析。

2.     mmc_claim_host

mmc_claim_host定义在/mmc/core/core.h中实际的代码是由__ mmc_claim_host来完成的,具体的实现如下:

150 static inline void mmc_claim_host(struct mmc_host *host)

151 {

152        __mmc_claim_host(host, NULL);

153 }

函数以非终止的方式调用,传递的abort实参为NULL。关于__ mmc_claim_host的内容将做详细分析,具体代码如下:

[mmc/core/core.c]

451 int __mmc_claim_host(struct mmc_host *host, atomic_t *abort)

452 {

453        DECLARE_WAITQUEUE(wait, current);

454        unsigned long flags;

455        int stop;

456

457        might_sleep();

458

459        add_wait_queue(&host->wq, &wait);

460        spin_lock_irqsave(&host->lock, flags);

461        while (1) {

462               set_current_state(TASK_UNINTERRUPTIBLE);

463               stop = abort ? atomic_read(abort) : 0;

464               if (stop || !host->claimed || host->claimer == current)

465                      break;

466               spin_unlock_irqrestore(&host->lock, flags);

467               schedule();

468               spin_lock_irqsave(&host->lock, flags);

469        }

470        set_current_state(TASK_RUNNING);

471        if (!stop) {

472               host->claimed = 1;

473               host->claimer = current;

474               host->claim_cnt += 1;

475        } else

476               wake_up(&host->wq);

477        spin_unlock_irqrestore(&host->lock, flags);

478        remove_wait_queue(&host->wq, &wait);

479        if (!stop)

480               mmc_host_enable(host);

481        return stop;

482 }

453行初始化一个等待节点,后面我们将看到他的作用。

457行might_sleep宏其实就是检查是否需要重新调度,如果是,则进行调度。一般都用在可能引发睡眠的上下文中,完成任务的抢占。

459行将新的等待节点加入到主机的host->wq等待对列中,主要这里只是说加入进来

464行满足以下条件之一该线程将不会睡眠直接跳出while循环,分别是设置了终止、host空闲可用或者拥有该host的是本线程。当符合以上三条中的任何一种情况,程序会跳转到470行,重新设置线程状态为运行态。下面我们在abort=0的条件下分两种情况来讨论上面的整段代码的执行情况:

l  情况一:如果此时host不是空闲状态,host-> claimed=1且host->claimer != current此时按常理来说本线程将等待,459行,462行以及467行这三行代码使得线程睡眠在host->wq等待队列上,什么时候唤醒后面说到。

l  情况二:此时host处于空闲状态host-> claimed=0,或者host拥有者就是当前这个线程,这时程序直接跳转到470行,重新将进程状态设为运行态,当然这时的471-475行也就得以执行了。这三个变量的重新赋值意味着本线程得到了Host的控制权,当然478行的等待队列中的元素也就该删除了,480行host开始工作,具体实现后面再分析。

最后,来看看这段代码的一个最特殊的情况abort!=0时,这种情况本身就是个bug,既要claime然后又让人家abort,这一点我是看不穿想不透了,不过476行的代码倒是有几分的人性化,如果像前面所说的有线程睡眠在了host->wq上了,那么这个时候wake_up(&host->wq);一旦发出就回唤醒那些等待host的线程重新申请资源,毕竟这个host较少,资源还是相当紧张的。当然这是这种变态社会中的一种极其变态的情况,真正正常的wake_up可能还是要等到mmc_release_host的时候,在这里只是先提一下。

最后的最后要说的是current这个东西,这个指的不是别人,正是前面一节我们谈到的那个块请求处理的内核线程,当然早在card的prob中有个mmc_blk_set_blksize里面也使用到了这个她,那个时候到底指向的谁就确实不知道了。

另外,刚才说到了mmc_host_enable这个函数,定义在/mmc/core/core.c中,还是来简单的看一下:

[mmc/core/core.c]

346 /**

347 *    mmc_host_enable - enable a host.

348 *    @host: mmc host to enable

349 *

350 *    Hosts that support power saving can use the 'enable' and 'disable'

351 *    methods to exit and enter power saving states. For more information

352 *    see comments for struct mmc_host_ops.

353 */

354 int mmc_host_enable(struct mmc_host *host)

355 {

356        if (!(host->caps & MMC_CAP_DISABLE))

357               return 0;

358

359        if (host->en_dis_recurs)

360               return 0;

361

362        if (host->nesting_cnt++)

363               return 0;

364

365        cancel_delayed_work_sync(&host->disable);

366

367        if (host->enabled)

368               return 0;

369

370        if (host->ops->enable) {

371               int err;

372

373               host->en_dis_recurs = 1;

374               err = host->ops->enable(host);

375               host->en_dis_recurs = 0;

376

377               if (err) {

378                      pr_debug("%s: enable error %d\n",

379                             mmc_hostname(host), err);

380                      return err;

381               }

382        }

383        host->enabled = 1;

384        return 0;

385 }

365行这实际上是内核维护的一个全局的工作队列,使用时不需要定义工作队列结构体,全局工作队列创建的时候可以使用如下方法:

int schedule_work(struct work_struct *work ); 
int schedule_work_on(intCPU,struct work_struct *work ); 
int scheduled_delayed_work(struct delayed_work *dwork,unsigned long delay); 
int scheduled_delayed_work_on(int cpu,struct delayed_work *dwork,unsigned long delay); 

如果任务被延迟,调用cancel_delayed_work_sync将会终止队列中的任务或者阻塞任务直到回调结束(如果处理程序已经在处理该任务)。从这里不难发现主机处理任务的方式很有可能是利用的全局工作队列,具体如何等见到创建队列任务的时候再说。

370-382行就是调用host所提供的mmc_host_ops方法来设置了,其实整个过程是和低功耗相关的,这里就不再深入研究了。

看完mmc_claim_host,我们乘热打铁把他的孪生兄弟mmc_release_host也给解决了。

3.     mmc_release_host

mmc_release_host与mmc_claim_host一起出生入死,始终是成对出现,执行的过程肯能在顺序上有点颠倒,上点源码如下:

[mmc/core/core.c]

575 void mmc_release_host(struct mmc_host *host)

576 {

577        WARN_ON(!host->claimed);

578

579        mmc_host_lazy_disable(host);

580

581        mmc_do_release_host(host);

582 }

579行跟进源码:

[mmc/core/core.c]

545 int mmc_host_lazy_disable(struct mmc_host *host)

546 {

547        if (!(host->caps & MMC_CAP_DISABLE))

548               return 0;

549

550        if (host->en_dis_recurs)

551               return 0;

552

553        if (--host->nesting_cnt)

554               return 0;

555

556        if (!host->enabled)

557               return 0;

558

559        if (host->disable_delay) {

560               mmc_schedule_delayed_work(&host->disable,

561                             msecs_to_jiffies(host->disable_delay));

562               return 0;

563        } else

564               return mmc_host_do_disable(host, 1);

565 }

整个过程与上面说说的mmc_host_enable正好相反。

559-560行如果主机进入低功耗有个延时过程,那么就通过全局工作队列来进行延时调度,其中mmc_schedule_delayed_work调用的不是别人正是queue_delayed_work(workqueue, work, delay),至于host->disable里面放着什么内容这是host那边的事,谈到了在论。

564行如果主机没有这个癖好直接可以disable那再好不过了,mmc_host_do_disable(host, 1);为您解决一切后顾之忧。

[mmc/core/core.c]

388 static int mmc_host_do_disable(struct mmc_host *host, int lazy)

389 {

390        if (host->ops->disable) {

391               int err;

392

393               host->en_dis_recurs = 1;

394               err = host->ops->disable(host, lazy);

395               host->en_dis_recurs = 0;

396

397               if (err < 0) {

398                      pr_debug("%s: disable error %d\n",

399                             mmc_hostname(host), err);

400                      return err;

401               }

402               if (err > 0) {

403                      unsigned long delay = msecs_to_jiffies(err);

404

405                      mmc_schedule_delayed_work(&host->disable, delay);

406               }

407        }

408        host->enabled = 0;

409        return 0;

410 }

由此可见这个忧患也并没达到什么程度,394行爽快调用了host提供的disable方法,但是405行又再次出现一个延时调用,这个解释要想合理只有等到明年春暖花开的季节我们分析host的时候了。总之,当等到了那一天一切真相都会水落石出。

回到mmc_release_host还剩下最后一行,mmc_do_release_host(host)。

[mmc/core/core.c]

509 static void mmc_do_release_host(struct mmc_host *host)

510 {

511        unsigned long flags;

512

513        spin_lock_irqsave(&host->lock, flags);

514        if (--host->claim_cnt) {

515               /* Release for nested claim */

516               spin_unlock_irqrestore(&host->lock, flags);

517        } else {

518               host->claimed = 0;

519               host->claimer = NULL;

520               spin_unlock_irqrestore(&host->lock, flags);

521               wake_up(&host->wq);

522        }

523 }

这段代码逻辑异常清晰,格式异常工整,一切犹如行云流水一般。环环相扣,句句入理。这么多的优点面前我只说一句,省的大煞风景。

521行再见了wake_up,前面说过一个变态的,这里出现的这个可是他家的正统血脉哦,一个主机控制器的release,wake_up了一批抢占他的线程,这里就是个强有力的证明。

好了,废话加白话总之没有一句真话的把mmc_release_host讲完了,接下来该步入整个SD卡故事得正题了,别笑的太早,到时候有你好受。

4.     mmc_wait_for_req

总算是轮到他了,不是不想说他,是说起他来估计说到天黑还没个底。但是无论怎样,天塌下来内核源代码都还是要看的。废话少说,先上源码:

[mmc/core/core.c]

184 /**

185 *    mmc_wait_for_req - start a request and wait for completion

186 *    @host: MMC host to start command

187 *    @mrq: MMC request to start

188 *

189 *    Start a new MMC custom command request for a host, and wait

190 *    for the command to complete. Does not attempt to parse the

191 *    response.

192 */

193 void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)

194 {

195        DECLARE_COMPLETION_ONSTACK(complete);

196

197        mrq->done_data = &complete;

198        mrq->done = mmc_wait_done;

199

200        mmc_start_request(host, mrq);

201

202        wait_for_completion(&complete);

203 }

我汗,这啥意思,咋整个代码比潘长江还短。罢了不管他,研究下代码。

200行mmc_start_request搞了半天才开始请求,你说这急人不急人,好了看你怎个start法。

[mmc/core/core.c]

121 static void

122 mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)

123 {

124 #ifdef CONFIG_MMC_DEBUG

125        unsigned int i, sz;

126        struct scatterlist *sg;

127 #endif

128

129        pr_debug("%s: starting CMD%u arg %08x flags %08x\n",

130               mmc_hostname(host), mrq->cmd->opcode,

131               mrq->cmd->arg, mrq->cmd->flags);

132

133        if (mrq->data) {

134               pr_debug("%s:     blksz %d blocks %d flags %08x "

135                      tsac %d ms nsac %d\n,

136                      mmc_hostname(host), mrq->data->blksz,

137                      mrq->data->blocks, mrq->data->flags,

138                      mrq->data->timeout_ns / 1000000,

139                      mrq->data->timeout_clks);

140        }

141

142        if (mrq->stop) {

143               pr_debug("%s:     CMD%u arg %08x flags %08x\n",

144                      mmc_hostname(host), mrq->stop->opcode,

145                      mrq->stop->arg, mrq->stop->flags);

146        }

147

148        WARN_ON(!host->claimed);

149

150        led_trigger_event(host->led, LED_FULL);

151

152        mrq->cmd->error = 0;

153        mrq->cmd->mrq = mrq;

154        if (mrq->data) {

155               BUG_ON(mrq->data->blksz > host->max_blk_size);

156               BUG_ON(mrq->data->blocks > host->max_blk_count);

157               BUG_ON(mrq->data->blocks * mrq->data->blksz >

158                      host->max_req_size);

159

160 #ifdef CONFIG_MMC_DEBUG

161               sz = 0;

162               for_each_sg(mrq->data->sg, sg, mrq->data->sg_len, i)

163                      sz += sg->length;

164               BUG_ON(sz != mrq->data->blocks * mrq->data->blksz);

165 #endif

166

167               mrq->cmd->data = mrq->data;

168               mrq->data->error = 0;

169               mrq->data->mrq = mrq;

170               if (mrq->stop) {

171                      mrq->data->stop = mrq->stop;

172                      mrq->stop->error = 0;

173                      mrq->stop->mrq = mrq;

174               }

175        }

176        host->ops->request(host, mrq);

177 }

这不看不大紧,一看乐死人。149行以前全是debug要用的,本来就不长的代码这一缩水可真没啥分量了。

150行啥玩意,LED?都啥时候了还有这闲工夫玩这玩意。搞硬件的兄弟们总喜欢在板子上搞几个LED,弄的像不整几个上去不足以展示自己实力似的,这也就苦了这帮子写内核的哥们,既然有了个状态指示灯,总不能让它没反应吧,省的那些不懂硬件的人怀疑哪位画板子的哥们把个LED的原理图给画错了,这就麻烦大了,会死人的。好吧就加段小代码让他工作起来吧,也就有了这个led_trigger_event,当然是可配置的。如果您确实认为有必要研究一番,就劳驾自己去观摩吧,我在这里就先失陪了。

167-173行这几行什么意思我始终没能明白,也许真的到了春暖花开的季节才能明白他的良苦用心吧。没办法,现在还不能说,那就等待等待在等待吧....

176行不用我多说肯定都知道这是个什么意思,不错调用的真是host边的接口,好了不能再说了,再说就有人告我侵犯领土完整了。

前面说了这个函数很复杂的,怎么?放心好戏在后头。好了回到mmc_wait_for_req...

202行wait_for_completion(&complete);典型的complete机制,不会吧没听说过,那只能说明你out了。内核同步机制中complete可是也占了部分江山的呀,wait_for_completion以后的结果就是调用线程你可以进入冬眠了,什么时候我这边做完了会利用complete来解脱你的。好了话都说到这份上了,您自己说这个等待的过程是不是很漫长,何况也没有个期限,即使一万年也得等啊。

197-198行mmc_wait_done等待完成,进去看看

[mmc/core/core.c]

179 static void mmc_wait_done(struct mmc_request *mrq)

180 {

181        complete(mrq->done_data);

182 }

181行确是complete,而且complete就是wait_for_completion的那个对象,好了大胆想象吧,N年后的某个地方我们肯定会与mmc_wait_done在聚首。不信就走着瞧....

5.     mmc_wait_for_cmd

是时候进入mmc_wait_for_cmd了,不过遗憾的是这个函数也确实没有太多吸引人眼球的地方,还是贴出他的源码来吧。

[mmc/core/core.c]

217 int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)

218 {

219        struct mmc_request mrq;

220

221        WARN_ON(!host->claimed);

222

223        memset(&mrq, 0, sizeof(struct mmc_request));

224

225        memset(cmd->resp, 0, sizeof(cmd->resp));

226        cmd->retries = retries;

227

228        mrq.cmd = cmd;

229        cmd->data = NULL;

230

231        mmc_wait_for_req(host, &mrq);

232

233        return cmd->error;

234 }

命令的提交形式与数据请求的有点区别,至少没能构建一个完整的struct mmc_request结构。但是最终却都是调用了mmc_wait_for_req。至于这些个struct mmc_command 、

struct mmc_reques现在确实不方便也没办法说清楚,说到真正作用在硬件上的传输过程之时,也将是揭开他神秘面纱之日。

故事发展到这里我们断了线索,但是有一点是肯定的core为我们所干的远不止这些,至少到目前为止我们只分析到了呈上所做的工作,至于启下会干那些工作等我们看完host再来给他画上圆满的句号,我想只有这样才能表达我们对这个吃苦耐劳的core最崇高的敬意。

http://m.blog.csdn.net/blog/rain0993/8476755

sd卡驱动分析之core相关推荐

  1. Linux SD卡驱动开发(五) —— SD 卡驱动分析Core补充篇

    Core层中有两个重要函数 mmc_alloc_host 用于构造host,前面已经学习过,这里不再阐述:另一个就是 mmc_add_host,用于注册host 前面探测函数s3cmci_probe, ...

  2. SD卡驱动分析(二)

    三.下面分析一下高通的android2.3的代码中SD卡驱动的流程. 在kernel中,SD卡是作为平台设备加入到内核中去的,在/kernel/arch/arm/mach-msm/devices-ms ...

  3. SD卡驱动分析(一)

    Android下的SD卡驱动与标准LINUX下的SD卡驱动好像没有太大的区别,这里就以高通的ANDROID 2.3以代表,来简要分析一下LINUX下SD卡驱动的写法.由于小弟的技术有限,分析的有错的地 ...

  4. linux SD卡驱动分析

    1. 硬件基础: SD/MMC/SDIO 概念区分概要 SD (Secure Digital )与 MMC (Multimedia Card ) SD 是一种 flash memory card 的标 ...

  5. S3C2440上MMC/SD卡驱动分析(二)

    下面的文章主要是转载的,先记录下自己的经验. MMC/SD驱动有两种模式:FIFO和DMA.在代码中两种方式都予以了实现,在make menuconfig时候,可以选择是使用fifo方式还是DMA方式 ...

  6. Linux SD卡驱动开发(二) —— SD 卡驱动分析HOST篇

    回顾一下前面的知识,MMC 子系统范围三个部分: HOST 部分是针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的. CORE 部分: 这是整个MMC 的核心存,这部分完成 ...

  7. sd 卡驱动--基于高通平台

    点击打开链接 内容来自以下博客: http://blog.csdn.net/qianjin0703/article/details/5918041 Linux设备驱动子系统第二弹 - SD卡 (有介绍 ...

  8. Linux SD卡驱动开发(六) —— SD卡启动过程总体分析

    一.工作流程 mmc驱动主要文件包括 drivers/mmc/card/block.c drivers/mmc/card/queue.c drivers/mmc/core/core.c drivers ...

  9. rt-thread SDIO驱动框架分析(SD卡驱动\SD Nand驱动)

    rt-thread SDIO驱动框架分析之SD卡驱动 文章目录 rt-thread SDIO驱动框架分析之SD卡驱动 1. 前言 2. SDIO通用驱动框架介绍 3. 文件架构分析 4. SDIO设备 ...

  10. 嵌入式linux sd卡读写,嵌入式Linux之我行——S3C2440上MMC/SD卡驱动实例开发讲解(二)...

    嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤.一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便.如有错误之处,谢请指正. 一.开发环境 主  机:VMWa ...

最新文章

  1. 2019 ICPC Asia Nanjing Regional K.Triangle(求一个能将三角形分成两个面积相同的线段、计算几何)
  2. 【Java】PMD规则学习(1) --字符串比较
  3. uni-calendar更改打点颜色实现签到和缺勤不同打点颜色效果
  4. eNSP仿真模拟与实际环境的几个不符点
  5. C#中用ToString方法格式化时间
  6. 自动控制原理复习——第二章 控制系统的数学模型,系统框图简化,信号流图,梅森公式,控制系统的传递函数(详细介绍)
  7. dstwo linux 模拟器,dstwo使用gba模拟器V1.30版本下载和使用作弊功能的作弊教程
  8. Pycharm_EmmyLua断点调试Lua
  9. 用ProcessOn在线作图
  10. Java 输出三角形
  11. TOM企业邮箱,为你打造企业专属邮箱
  12. java 用户态_内核启动用户态的程序 - 但行好事 莫问前程 - JavaEye技术网站
  13. vim插入(insert)模式下的快捷键
  14. Java - 加号(+)的作用
  15. 微信聊天记录丢失后的记录(二)
  16. 最新的VMware Workstation Pro 17安装教程+安装包下载
  17. NLP自然语言处理中oov的词的解释
  18. jq实现导航吸顶效果
  19. 同一个excel文件在不同电脑上打印高度不同的原因
  20. 使用dlsym动态加载库函数、封装原有库函数

热门文章

  1. 最强PostMan使用教程(7)postman做数字签名认证
  2. java NBA2010,关于一个NBA球队连续夺冠的SQL查询问题,解法很精妙~
  3. 微信养号技巧及防封攻略(微信养号防封大全)
  4. 【Get深一度】信号处理(三)——3db带宽
  5. 影响宝宝脾胃健康的3个“真凶”,难怪孩子脾胃总是调不好!
  6. 以文会友,以书传情—山西省侨商联合会召开书画艺术委员会成立大会
  7. wdr7300百兆版_TPLINK WDR7660千兆版无线路由器拆机
  8. python实现等量随机分组
  9. 《基因大数据智能生产及分析》笔记
  10. lucene索引源码分析1