linux4.4emmc驱动

2017-08-18 12:32:29 kris_fei 阅读数 6729

Chipset: msm8926
OS: Android4.4
Kernel: 3.4.0

在N久之前的会议上留下了两个问题:
1. 内核在加载emmc驱动时是如何知道当前有几个分区的?
2. 如何知道/dev/mmcblkXpX对应的是哪个分区呢?

读取分区

加载sd/emmc驱动的时候,有如下流程:
这里写图片描述
来看看Check_partition():
这里写图片描述

核心的部分是check_part这个结构体,里面放的都是对应的解析分区的算法:
这里写图片描述
算法不是我们需要关注的,解析哪一块区域等小弟看了partition再给大家解释吧。
解析完成之后放到struct parsed_partitions也就是这里的state变量的parts元素里:
这里写图片描述
然后通过add_partition()将这些part信息添加到disk里去。

有多少分区可以从开机Log看到:
这里写图片描述
确实我们在8926平台上定义的分区个数是34个:
[non_hlos\common\build\partition_f5.xml]
这里写图片描述

分区对应label

其实Linux已经在/dev/block下提供了查看方法:
这里写图片描述

从本质上来说,这些信息是从解析出来的struct parsed_partitions中的info变量获取的:
这里写图片描述
对应的volname存的就是分区的label name.

                                                    Kris.Fei
                                                    2014/09/11
2019-03-08 16:29:00 weixin_30699465 阅读数 43

开发环境:win10 64位 + VMware12 + Ubuntu14.04 32位

工具链:linaro提供的gcc-linaro-6.1.1-2016.08-x86_64_arm-linux-gnueabi

要移植的kernel版本:linux-4.4

Tiny4412开发板硬件版本为

  底板:  Tiny4412SDK 1312B

  核心板:Tiny4412 - 1306

 

1、下载、解压内核

linux-4.4.tar.gz 

2、配置

  1)配置开发板类型及指定交叉编译链

diff --git a/arch/arm/boot/dts/exynos4412-tiny4412.dts b/arch/arm/boot/dts/exyno
index 4840bbd..048ae67 100644
--- a/arch/arm/boot/dts/exynos4412-tiny4412.dts
+++ b/arch/arm/boot/dts/exynos4412-tiny4412.dts
@@ -21,6 +21,7 @@
 
 ESCOD
index 70dea02..42b0d63 100644
--- a/Makefile
+++ b/Makefile
@@ -248,8 +248,11 @@ SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_6
 # "make" in the configured kernel build directory always uses that.
 # Default value for CROSS_COMPILE is not to prefix executables
 # Note: Some architectures assign CROSS_COMPILE in their arch/*/Makefile
-ARCH           ?= $(SUBARCH)
-CROSS_COMPILE  ?= $(CONFIG_CROSS_COMPILE:"%"=%)
+#ARCH          ?= $(SUBARCH)
+#CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
+
+ARCH           ?= arm
+CROSS_COMPILE  ?= /usr/local/ARM-toolchian/gcc-linaro-6.1.1-2016.08-i686_arm-linux-gnueabi/bin/arm-linux-gnueabi-
 
 # Architecture as present in compile.h
 UTS_MACHINE    := $(ARCH)

  

  2)修改设备树

diff --git a/arch/arm/boot/dts/exynos4412-tiny4412.dts b/arch/arm/boot/dts/exyno
index 4840bbd..048ae67 100644
--- a/arch/arm/boot/dts/exynos4412-tiny4412.dts
+++ b/arch/arm/boot/dts/exynos4412-tiny4412.dts
@@ -21,6 +21,7 @@
 
        chosen {
                stdout-path = &serial_0;
+        bootargs="root=/dev/ram0 rw rootfstype=ext4 console=ttySAC0,115200 init=/linuxrc earlyprintk";
        };
 
        memory {
@@ -78,7 +79,8 @@
        bus-width = <4>;
        pinctrl-0 = <&sd2_clk &sd2_cmd &sd2_cd &sd2_bus4>;
        pinctrl-names = "default";
-       status = "okay";
+   /* status = "okay";*/
+    status = "disabled";
 };
 
 &serial_0 {

这里关键的一点是在chosen中增加了bootargs的设置,上面设置bootargs表示的意思是:根文件系统是ramdisk,可读写,文件系统类型是ext4格式,串口终端使用ttySAC0,波特率是115200,earlyprintk用于打印内核启动早期的一些log,它会把printk的信息打印到一个叫做bootconsole的终端上,在真正的console注册后,bootconsole会被disable掉,要想使用earlyprintk,需要在内核中做相关的配置,这个下面再说。bootargs的设置很灵活,既可以在内核的设备树中设置,也可以在u-boot中设置,需要注意的是:如果在u-boot中设置了bootargs的话,在bootm的时候u-boot会用自己的bootargs来覆盖设备树里的bootargs( do_bootm_linux -> bootm_linux_fdt -> fdt_chosen)。还有一点是把SD卡控制器2给禁掉了,目前SD控制器的初始化还有些问题,会导致内核挂掉,这个以后再解决,因为我们将来先用ramdisk做根文件系统,跟eMMC和SD卡都没有关系

3,配置内核支持Ramdisk

make menuconfig
File systems --->;
    <*> Second extended fs support
Device Drivers
    SCSI device support --->;
        <*> SCSI disk support
    Block devices --->;
        <*>RAM block device support
        (16)Default number of RAM disks
        (8192) Default RAM disk size (kbytes) (修改为8M)
General setup --->;
    [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support

这个exynos的默认配置就已经支持了。

4.配置内核支持Tmpfs

$ make menuconfig

File systems --->;
   Pseudo filesystems --->
        [*] Virtual memory file system support (former shm fs)
        [*] Tmpfs POSIX Access Control Lists
这个exynos的默认配置也已经支持了。

5.编译内核

第一次编译需要获取exynos配置

  make exynos_defconfig

root@ubuntu:/home/arm/linux-4.4# cat build.sh 
#!/bin/bash

echo "---------------"
echo "rm ./ uImage and dtb"
rm uImage exynos4412-tiny4412.dtb

echo "make"
make uImage LOADADDR=0x40007000 -j4

echo "make dtb"
make dtbs

echo "backup config"
cp .config tiny4412_defconfig

echo "copy uImage"
cp arch/arm/boot/uImage ./

echo "copy dtb"
cp arch/arm/boot/dts/exynos4412-tiny4412.dtb ./

echo "over"

编写脚本。执行,烧写

即可初步启动kernel

---------------------------------------------------------------------------------------------------------------------------------------------------------------

问题:启动后如果显示文件太多会出现

dma-pl330 12680000.pdma: Reset Channel-1 CS-20000f FTC-20000
[ 1573.447654] dma-pl330 12680000.pdma: Reset Channel-1 CS-20000f FTC-20000

参考:https://blog.csdn.net/sinat_20006769/article/details/79060779

说是DMA驱动的问题,按照介绍取消dma-pl330后,kernel无法启动

参考:https://blog.csdn.net/hyyoxhk/article/details/83305367

修改arch/arm/boot/dts/exynos4412-tiny4412.dts中,serial_0的dmas导致卡死,serial_2的dmas无效果

参考:https://blog.csdn.net/qq_25370227/article/details/84891632

是uboot中TrustZone造成,目前未尝试

--------------------------------------------------------------------------------------------------------------------------------------------------------------

已解决:就是TrustZone造成,具体查看:linux-kernel-4.4 移植 (2)解决上部遗留DMA-PL330的问题

————————————————————————————————————————————————

整体参考:

https://www.cnblogs.com/pengdonglin137/p/5143516.html

https://blog.csdn.net/sinat_20006769/column/info/19139

转载于:https://www.cnblogs.com/chu-yi/p/10496621.html

2016-02-27 11:51:40 yanleizhouqing 阅读数 6114
EMMC 驱动,emmc驱动

转载来源:http://www.bkjia.com/Androidjc/944793.html


        块设备是Linux最复杂的设备之一,但是作为固执于知其然的Geek,我们总会把代码翻个遍,把道理弄个透。当然了,快速地学习一种新的东西,方法是最重要的,个人觉得: 内核当中 MMC/SD 卡驱动程序构架是学习EMMC 驱动程序的重点,只有理解了它才能真正理解该块设备驱动程序,同时才能真正理解 LINUX 块设备驱动程序。


 


一.需要的基础知识:


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


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


3.       LINUX 设备驱动模型。


4.       EMMC的原理,是Nand Flash的基础上加上一个负责:ECC、负载均衡和坏块管理功能的controler。


 


二.驱动程序分析


       首先,说明一下EMMC驱动涉及到的文件。另外,我们重点是分析驱动程序的基本构架,所以不同内核版本的差异并不是很大。 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 卡的驱动分析完了,是不是有些复杂,不过这样设计的目的是为了分层,让具体平台的驱动编写更加省事。
2019-10-14 11:30:53 qq543716996 阅读数 78
硬件:
    核心板  : Tiny4412-1306-Schematic
    底板    : Tiny4412SDK-1308(没有micro USB)其他都一样

参考博客:https://www.cnblogs.com/pengdonglin137/p/5143516.html

在此感谢前辈分享。

代码已上传至github上管理。https://github.com/Jason543716996/tiny4412.git

使用完别忘记点赞,关注,方便交流学习

一、修改设备树文件

diff --git a/arch/arm/boot/dts/exynos4412-tiny4412.dts b/arch/arm/boot/dts/exynos4412-tiny4412.dts
index 4840bbd..013f52f 100644
--- a/arch/arm/boot/dts/exynos4412-tiny4412.dts
+++ b/arch/arm/boot/dts/exynos4412-tiny4412.dts
@@ -18,7 +18,7 @@
 / {
        model = "FriendlyARM TINY4412 board based on Exynos4412";
        compatible = "friendlyarm,tiny4412", "samsung,exynos4412", "samsung,exynos4";
-
+       bootargs = "root=/dev/ram0 rw rootfstype=ext4 console=ttySAC0,115200 init=/linuxrc earlyprintk";
        chosen {
                stdout-path = &serial_0;
        };
@@ -78,7 +78,7 @@
        bus-width = <4>;
        pinctrl-0 = <&sd2_clk &sd2_cmd &sd2_cd &sd2_bus4>;
        pinctrl-names = "default";
-       status = "okay";
+       status = "disabled";
 };

二、编译

    export ARCH=arm
    export CROSS_COMPILE=arm-none-linux-gnueabi-
    
    make exynos_defconfig
    make uImage LOADADDR=0x40008000 -j2
    make dtbs

三、配置u-boot环境变量

将编译好的内核:uImage 
        设备树:exynos4412-tiny4412.dtb
        根文件系统镜像:ramdisk.img 放入内存指定内存地址。
由于1308的底板没有micro USB,可以选择用loadb 将内核、设备树、根文件系统镜像放入指定位置,或者读取SDMMC
这里选择通过fatload读取:
    set bootcmd "fatload mmc 1 40500000 uImage;fatload mmc 1 41000000  ramdisk.img;fatload mmc 1 42000000 exynos4412-tiny4412.dtb;bootm  0x40500000 0x41000000 0x42000000"
    save
    boot
    可以看到已经启动了内核。

================================================================================================

但是,由于没有加载SDMMC  或者 EMMC,看到的文件系统分区的空间很小。

       接下来,移植网卡驱动,通过网络文件系统进行调试,最后可以移植SD卡驱动或者MMC,将内核、设备树、根文件系统放入MMC中存储,运行。

 

2016-07-21 11:04:34 BOJUE01 阅读数 1612
Linux3.0.35内核目前只支持emmc4.5以下,所有使用最新的emmc5.0以上就需要作出以下修改:
drivers/mmc/core/mmc.c

 --- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -260,7 +260,7 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
 
        card->ext_csd.rev = ext_csd[EXT_CSD_REV];
        /* workaround: support emmc 4.5 cards to work at emmc 4.4 mode */
-       if (card->ext_csd.rev > 6) {
+       if (card->ext_csd.rev > 8) {
                printk(KERN_ERR "%s: unrecognised EXT_CSD revision %d\n",
                        mmc_hostname(card->host), card->ext_csd.rev);
                err = -EINVAL;