废话不多说,直接切进主题:

Linux在内核源码的drivers/mmc/core文件夹下为我们的提供了一系列SD卡的接口服务函数。可以查看Makefile如下

可见,core文件夹下有针对总线的服务bus.c,针对主控制器的服务host.c,针对SD卡的服务sd.c, sd_ops.c等等。

其中,最为核心的一个函数便是之前提到的位于core.c的mmc_rescan,概括来讲,主要完成两项任务,即

扫描SD总线,插入SD卡
扫描SD总线,拔出SD卡

一、 插入SD卡

前面HOST篇最后的中断篇中讲到,插入SD卡,主控制器产生中断,进入中断处理函数s3cmci_irq_cd,其中调用的函数 mmc_detect_change,它将最终调用queue_delayed_work执行工作队列里的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;if (host->rescan_disable)return;/* If there is a non-removable card registered, only scan once */if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered)return;host->rescan_entered = 1;mmc_bus_get(host);/** if there is a _removable_ card registered, check whether it is* still present*/if (host->bus_ops && host->bus_ops->detect && !host->bus_dead&& !(host->caps & MMC_CAP_NONREMOVABLE))host->bus_ops->detect(host);host->detect_change = 0;/** Let mmc_bus_put() free the bus/bus_ops if we've found that* the card is no longer present.*/mmc_bus_put(host);mmc_bus_get(host);/* if there still is a card present, stop here */if (host->bus_ops != NULL) {mmc_bus_put(host);goto out;}/** Only we can add a new handler, so it's safe to* release the lock here.*/mmc_bus_put(host);if (!(host->caps & MMC_CAP_NONREMOVABLE) && host->ops->get_cd &&host->ops->get_cd(host) == 0) {mmc_claim_host(host);mmc_power_off(host);mmc_release_host(host);goto out;}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);
}

插入SD卡,mmc_rescan扫描SD总线上是否存在SD卡,具体的实现方法就是通过向SD卡上电,看是否能成功,以普通SD卡为例,为普通SD卡上电的函数mmc_send_app_op_cond(host, 0, &ocr);

如果上电成功,则返回0,即执行if()里的mmc_attach_sd()进行总线与SD卡的绑定

如果上电失败,则返回非0值,跳过if(),尝试其他上电的方法。

那么,上电方法究竟有何不同呢?具体看看mmc_send_app_op_cond()的实现过程

int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
{struct mmc_command cmd;cmd.opcode = SD_APP_OP_COND;    /* #define SD_APP_OP_COND   41   */mmc_wait_for_app_cmd(host, NULL, &cmd, MMC_CMD_RETRIES);... ...}
int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, struct mmc_command *cmd, int retries)
{mmc_app_cmd(host, card);   /* #define MMC_APP_CMD   55   */mrq.cmd = cmd;cmd->data = NULL;mmc_wait_for_req(host, &mrq);... ...}

这里的指令SD_APP_OP_COND只有SD2.0的协议支持,也就是说,只有普通SD卡支持,所以也只有普通SD卡能够成功上电。 

如果上电成功,就开始进行总线与SD卡的绑定,通过mmc_attach_sd(),绑定过程可分为四步,

注册SD总线上的操作函数 - struct mmc_bus_ops mmc_sd_ops

设置主控制器的时钟和总线方式 - 通过回调函数host->ops->set_ios();

启动SD卡 - 根据协议,完成SD卡启动的各个步骤

注册SD卡设备驱动

二、注册总线上的操作函数

int mmc_attach_sd(struct mmc_host *host, u32 ocr)
{mmc_sd_attach_bus_ops(host);... ...}
static void mmc_sd_attach_bus_ops(struct mmc_host *host)
{const struct mmc_bus_ops *bus_ops;bus_ops = &mmc_sd_ops;mmc_attach_bus(host, bus_ops);
}
void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
{host->bus_ops = ops;host->bus_refs = 1;host->bus_dead = 0;
}
static const struct mmc_bus_ops mmc_sd_ops = {.remove = mmc_sd_remove,  // 拔出SD卡的操作函数.detect = mmc_sd_detect,      // 探测SD卡的操作函数.suspend = NULL,.resume = NULL,.power_restore = mmc_sd_power_restore,  // 重新启动SD卡的操作函数
};

这里的mmc_sd_detect和mmc_sd_remove就是拔出SD卡所需要用到的函数,下文将详细讨论。这里需要注意的是,插入SD卡的时候,并不执行mmc_sd_detect和mmc_sd_remove这两个函数,但是会注册它们,也就是说,这两个函数的功能已经实现,将来可以使用。

三、设置时钟和总线

int mmc_attach_sd(struct mmc_host *host, u32 ocr)
{host->ocr = mmc_select_voltage(host, ocr);... ...}u32 mmc_select_voltage(struct mmc_host *host, u32 ocr)
{mmc_set_ios(host);... ...
}static inline void mmc_set_ios(struct mmc_host *host)
{struct mmc_ios *ios = &host->ios;host->ops->set_ios(host, ios);  // 设置主控制器时钟和总线的回调函数,具体实现由主控制器驱动完成
}

从这里可以体会到回调函数的精髓:协议层里利用回调函数为所有满足该协议的设备提供统一的接口,而具体实现由底层不同的设备驱动各自完成。注意到,之所以要定义一些放之四海而皆准的公用的类,比如struct mmc_host,就是需要通过struct mmc_host *host指针作为形参传到协议层所提供的接口函数中,从而得以调用。


四、启动SD卡

int mmc_attach_sd(struct mmc_host *host, u32 ocr)
{mmc_sd_init_card(host, host->ocr, NULL);... ...}

mmc_sd_init_card主要完成以下任务,

SD卡的启动过程
得到寄存器CID, CSD, SCR, RCA的数据
其他操作比如切换到高速模式,初始化card

static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, struct mmc_card *oldcard)
{/* SD卡的启动过程 */mmc_go_idle(host);mmc_send_if_cond(host, ocr);mmc_send_app_op_cond(host, ocr, NULL);mmc_all_send_cid(host, cid);mmc_send_relative_addr(host, &card->rca);/* 得到寄存器CID, CSD, SCR的数据 */mmc_send_csd(card, card->raw_csd);mmc_decode_csd(card);mmc_decode_cid(card);mmc_app_send_scr(card, card->raw_scr);mmc_decode_scr(card);/* 其它操作 */mmc_alloc_card(host, &sd_type);mmc_select_card(card); mmc_read_switch(card);mmc_switch_hs(card);... ...}

1) SD卡的启动过程

根据SD2.0协议,SD卡的状态可分为两种模式:卡识别模式(card-identification mode)和数据传输模式(data-transfer mode)。这里,我们关注启动SD卡的卡识别模式。

综合代码:

  mmc_go_idle(host);                     CMD0Idle Statemmc_send_if_cond(host, ocr);     CMD8mmc_send_app_op_cond(host, ocr, NULL);       ACMD41Ready Statemmc_all_send_cid(host, cid);       CMD2Identification Statemmc_send_relative_addr(host, &card->rca);     CMD3Stand-by State

2) 寄存器CID, CSD, SCR, RCA

-> 发送指令并得到寄存器的值

当主控制器向SD卡发送cmd指令,比如mmc_send_cid(card, card->raw_cid),请求得到SD卡CID寄存器的值,当主控制器发送cmd完成后,芯片产生一个内部中断,处理结束cmd的中断函数,之后得到来自SD卡的response,即CID寄存器的值,存放于host->cmd->resp[i]中。关于内部中断处理,参看上文的中断一节里的 mmc_wait_for_cmd()

mmc_send_cid(card, card->raw_cid);这个函数发送了接收CSD寄存器的请求,并且得到了来自SD卡的CSD寄存器的值。

int mmc_send_cid(struct mmc_card *card, u32 *cid)
{return mmc_send_cxd_native(card->host, card->rca << 16, cid, MMC_SEND_CID);}static int mmc_send_cxd_native(struct mmc_host *host, u32 arg, u32 *cxd, int opcode)
{cmd.opcode = opcode;cmd.arg = arg;cmd.flags = MMC_RSP_R2 | MMC_CMD_AC;mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES);memcpy(cxd, cmd.resp, sizeof(u32) * 4);  // 得到response赋给cxd,即card->raw_cid... ...
}

-> 解析寄存器的值

为什么要解析?先来看看寄存器CID在SD卡协议里的定义,它是一个128位的寄存器,存放了关于这块SD卡的基本信息,就像自己的身份证。通过mmc_send_cid()将这个寄存器的数值赋给了card->raw_cid (定义 u32 raw_cid[4];) ,为了方便得到具体某一个信息,协议层为我们解析了寄存器里的域,并赋给card->cid,比如厂商名称,就可以通过card->cid.manfid直接读取到。

static int mmc_decode_cid(struct mmc_card *card)
{u32 *resp = card->raw_cid;card->cid.manfid = UNSTUFF_BITS(resp, 120, 8);card->cid.oemid  = UNSTUFF_BITS(resp, 104, 16);card->cid.prod_name[0] = UNSTUFF_BITS(resp, 96, 8);card->cid.prod_name[1] = UNSTUFF_BITS(resp, 88, 8);card->cid.prod_name[2] = UNSTUFF_BITS(resp, 80, 8);card->cid.prod_name[3] = UNSTUFF_BITS(resp, 72, 8);card->cid.prod_name[4] = UNSTUFF_BITS(resp, 64, 8);card->cid.prod_name[5] = UNSTUFF_BITS(resp, 56, 8);card->cid.serial = UNSTUFF_BITS(resp, 16, 32);card->cid.month  = UNSTUFF_BITS(resp, 12, 4);card->cid.year  = UNSTUFF_BITS(resp, 8, 4) + 1997;return 0;
}

五、 注册SD卡设备驱动

 int mmc_attach_sd(struct mmc_host *host, u32 ocr)
{/* mmc_alloc_card(host, &sd_type); 在mmc_sd_init_card()已完成 */mmc_add_card(host->card);... ...}

上文已经提到,设备驱动程序都会通过alloc_xxx()和add_xxx()两步来注册驱动,其实质是调用/drivers/base/core.c里的device_initialize()和device_add(),device_add()完成建立kobject,sys文件,发送uevent,等工作。

六、拔出SD卡

 void mmc_rescan(struct work_struct *work)
{struct mmc_host *host = container_of(work, struct mmc_host, detect.work);mmc_bus_get(host);/* if there is a card registered, check whether it is still present */if ((host->bus_ops != NULL) && host->bus_ops->detect && !host->bus_dead)host->bus_ops->detect(host);mmc_bus_put(host);... ...}

这里的mmc_bus_get/put(),为SD总线加上一个自旋锁,规定同时只能有一个线程在SD总线上操作。

1、 bus_ops->detect()

mmc_rescan()扫描SD总线,如果发现host->ops上赋了值,即之前已有SD卡注册过,就执行bus_ops->detect()操作去探测SD总线上是否还存在SD卡,如果不存在了,就执行bus_ops->remove()拔出SD卡。之前已经提到,这个bus_ops->detect()已在mmc_attach_sd()注册完成了。

static void mmc_sd_detect(struct mmc_host *host)
{mmc_claim_host(host);/** Just check if our card has been removed.*/err = mmc_send_status(host->card, NULL);mmc_release_host(host);if (err) {mmc_sd_remove(host);mmc_claim_host(host);mmc_detach_bus(host);mmc_release_host(host);}
}

这里的mmc_claim_host(host)通过set_current_state(TASK_RUNNING);将当前进程设置为正在运行进程。

mmc_send_status()发送得到SD卡状态的请求,如果未能得到状态数据,则执行mmc_sd_remove(host)拔出SD卡。

int mmc_send_status(struct mmc_card *card, u32 *status)
{struct mmc_command cmd;cmd.opcode = MMC_SEND_STATUS;    /* #define MMC_SEND_STATUS   13 */cmd.arg = card->rca << 16;cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES);if (err)               return err;           // 接收来自SD卡的response失败,即没有发现SD卡if (status)*status = cmd.resp[0];return 0;}

2、bus_ops->remove()

拔出SD卡,其实就是注册SD卡驱动的反操作,实质就是执行device_del()和device_put()

static void mmc_sd_remove(struct mmc_host *host)
{mmc_remove_card(host->card);host->card = NULL;
}
void mmc_remove_card(struct mmc_card *card)
{if (mmc_card_present(card))device_del(&card->dev);put_device(&card->dev);
}

Linux SD卡驱动开发(三) —— SD 卡驱动分析CORE篇相关推荐

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

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

  2. Linux下驱动开发_块设备驱动开发(硬件上采用SD卡+SPI协议)

    一.前言 块设备主要为存储设备设计的框架. 在前面章节Linux下驱动开发_块设备驱动开发(内存模拟存储) 里介绍了块设备驱动编写思路,并且利用内存模拟了硬件存储,完成了块设备驱动开发测试.这一篇文章 ...

  3. Linux驱动开发(外传)---驱动开发调试方法

    前文回顾 <Linux驱动开发(一)-环境搭建与hello world> <Linux驱动开发(二)-驱动与设备的分离设计> <Linux驱动开发(三)-设备树> ...

  4. Linux驱动开发:字符设备驱动开发实战

    Linux驱动开发:字符设备驱动开发实战 一.工程创建 VSCode 创建工程,设置 C/C++ 配置,导入 linux kernel 源码目录,方便 vscode 写代码自动补全,vscode 配置 ...

  5. Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)

    文章目录 全系列传送门 引言 驱动介绍 Hello World 1. 包含头文件 2. 驱动模块的入口和出口 3. 声明信息 4. 功能实现 完整代码 编译 第一种方法 第二种方法 编译成模块 第一步 ...

  6. i.MX 6ULL 驱动开发 六:beep 驱动

    一.原理分析 通过原理图可以确定 beep 连接到 SNVS_TAMPER1 引脚上.根据 beep 原理,当 SNVS_TAMPER1 输出低电平时,beep 鸣叫. 通过数据手册确定 SNVS_T ...

  7. STM32MP157驱动开发——多点电容触摸屏驱动

    STM32MP157驱动开发--多点电容触摸屏驱动 一.简介 二.电容触摸屏驱动框架简介 多点触摸(MT)协议详解 三.驱动开发 1.添加 FT5426 设备节点 2.FT5426 节点配置 3.驱动 ...

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

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

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

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

  10. Linux SD卡驱动开发(四) —— SD 控制器之真正的硬件操作

    前面对SD卡控制器有了一个基本的介绍.其实SD控制器层更过的意义是为core层提供一种操作SD卡硬件的一种方法,当然不同的控制器对硬件控制的方法不尽相同,但是他们最终都能像core层提交一个统一的封装 ...

最新文章

  1. 教你如何用阿里canal
  2. Exchange 默认数据库删除问题
  3. Jzoj4840 小W砍大树
  4. c语言调用android surface,Android GUI SurfaceFlinger
  5. Virtuoso崩掉时layout数据恢复
  6. 设计模式——解释器模式
  7. 用200行Go代码写一个自己的区块链!
  8. deeplin显示安装空间不够_电视内存不够怎么办?一步到位来搞定
  9. 数据结构10——强连通
  10. Linux系统移植概述
  11. fedora 14 root登陆修改方法
  12. oracle临时表怎么删除吗,删除Oracle临时表
  13. 不透明度16进制值对照表
  14. js 实现一个打点计时器
  15. 《用 Python 处理 Excel 数据之正则表达式视频教程》 曾贤志
  16. nums和nums[:]
  17. python中readlines是什么意思_python中read、readline、readlines之间的区别
  18. alin的学习之路:Qt与多线程
  19. 2019 斯坦福 CS224n 课程
  20. js变量控制css样式

热门文章

  1. centos7双网卡上不了外网
  2. StaMPS 在 SBAS 第一步报错 unable to read file ‘../slcosfactor.1.in‘ no such file or directory
  3. linux下wav转换为mp3
  4. 用ffmpeg批量转换WAV文件采样率
  5. 3d max 快捷键
  6. 刘济舟:《基于IAST交互式安全测试实践的初步探索》
  7. 安装新版的winetricks_winetricks中文修改版(wine模拟器)
  8. React通用解决方案——浮层容器
  9. java jbutton方法_java-如何设置JButton的大小?
  10. 服务器系统含5用户是什么意思,你好,想问下,我现在用的是T1商贸宝批发零售版5用户的,现在服务器是用WIN7,32位的系统,如果我的服务器升级为WIN10系统可不可以用?...