分类: LINUX
2010-11-03 19:39:38
Mini2440 SDcard启动分析 | |
mini2440驱动分析系列之 ---------------------------------------Mini2440 SD卡驱动程序分析 By JeefJiang July,25th,2009 Email:Jeefjiang1983@163.com (本文仅做学习交流用,转载或引用请标明) 前 几天把mini2440的sd卡驱动程序移植到了Android平台,当时对SD卡以及内核的MMC子系统不是很了解,浏览了四天的代码,终于理清了一些 头绪,尽管很多细节的实现还不是很清楚,不过先把知道的记录下来,细节部分由时间在慢慢挖掘。本文先介绍了一下MMC的基本框架结构,然后采用自底向上的 方法来分析整个MMC层是如何共同作用的。阅读时请结合参考资料1和2. 参考资料: 1.SD Memory Card Specifications / Part 1. Physical Layer Specification; Version 1.0 2.LDD3 CHAPTER-16 BLOCK DEVICE 3. 1.硬件基础: http://blog.ednchina.com/yelov/198217/message.aspx 2.MMC子系统的基本框架结构: 很遗憾,内核没有为我们提供关于MMC子系统的文档,在谷歌上搜索了很多,也没有找到相关文章。只能自己看代码分析了,可能有很多理解不对的地方,希望研究过这方面的朋友多邮件交流一下。 MMC子系统的代码在kernel/driver/MMC下,目前的MMC子系统支持一些形式的记忆卡:SD,SDIO,MMC.由于笔者对SDIO的规范不是很清楚,后面的分析中不会涉及。MMC子系统范围三个部分: HOST部分是针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的。 CORE部分:这是整个MMC的核心存,这部分完成了不同协议和规范的实现,并为HOST层的驱动提供了接口函数。 CARD部分:因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的SD卡如何实现为块设备的。 3.HOST层分析: HOST 层实现的就是我们针对特定主机的驱动程序,这里以mini2440的s3cmci.c为例子进行分析,我们先采用 platform_driver_register(&s3cmci_2440_driver)注册了一个平台设备,接下来重点关注probe函 数。在这个函数总,我们与CORE的联系是通过下面三句实现的。首先分配一个mmc_host结构体,注意sizeof(struct s3cmci_host),这样就能在mmc_host中找到了s3cmci_host,嵌入结构和被嵌入的结构体能够找到对方在Linux内核代码中的 常用技术了。接下来为mmc->pos赋值,s3cmci_ops结构实现了几个很重要的函数,待会我一一介绍。中间还对mmc结构的很多成员进行 了赋值,最后将mmc结构加入到MMC子系统,mmc_alloc_host,以及mmc_add_host的具体做了什么事情,我们在下节再分析,这三 句是些MMC层驱动必须包含的。 mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); mmc->ops = &s3cmci_ops; …………… s3cmci_ops中包含了四个函数: static struct mmc_host_ops s3cmci_ops = { .request = s3cmci_request, .set_ios = s3cmci_set_ios, .get_ro = s3cmci_get_ro, .get_cd = s3cmci_card_present, }; 我们从简单的开始分析,这些函数都会在core部分被调用: s3cmci_get_ro:这个函数通过从GPIO读取,来判断我们的卡是否是写保护的 s3cmci_card_present:这个函数通过从GPIO读取来判断卡是否存在 s3cmci_set_ios:s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) 依据核心层传递过来的ios,来设置硬件IO,包括引脚配置,使能时钟,和配置总线带宽。 s3cmci_request:这个函数是最主要,也最复杂的函数,实现了命令和数据的发送和接收, 当CORE部分需要发送命令或者传输数据时,都会调用这个函数,并传递mrq请求。 static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct s3cmci_host *host = mmc_priv(mmc); host->status = "mmc request"; host->cmd_is_stop = 0; host->mrq = mrq; if (s3cmci_card_present(mmc) == 0) { dbg(host, dbg_err, "%s: no medium present ", __func__); host->mrq->cmd->error = -ENOMEDIUM; mmc_request_done(mmc, mrq);//如果卡不存在,就终止请求 } else s3cmci_send_request(mmc); } 接下来看s3cmci_send_request(mmc): 这个函数先判断一下请求时传输数据还是命令,如果是数据的话: 先调用s3cmci_setup_data来对S3C2410_SDIDCON寄存器进行设置,然后设置SDITIMER寄存器这就设置好了总线宽度,是否使用DMA,,并启动了数据传输模式,并且使能了下面这些中断: imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC | S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH; 解析来判断是否是采用DMA进行数据传输还是采用FIFO进行数据传输 if (host->dodma) / because host->dodma = 0,so we don't use it res = s3cmci_prepare_dma(host, cmd->data);//准备DMA传输, else res = s3cmci_prepare_pio(host, cmd->data);.//准备FIFO传输 如果是命令的话:则调用s3cmci_send_command()这个函数是命令发送的函数,和datesheet上描述的过程差不多,关于SD规范中命令的格式,请参考参考资料1. writel(cmd->arg, host->base + S3C2410_SDICMDARG);/*先写参数寄存器 ccon = cmd->opcode & S3C2410_SDICMDCON_INDEX;//确定命令种类 ccon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART; /*with start 2bits*/ if (cmd->flags & MMC_RSP_PRESENT) ccon |= S3C2410_SDICMDCON_WAITRSP; /*wait rsp*/ if (cmd->flags & MMC_RSP_136) ccon |= S3C2410_SDICMDCON_LONGRSP; //确定respose的种类 writel(ccon, host->base + S3C2410_SDICMDCON); 命令通道分析完了,我们分析数据通道,先分析采用FIFO方式传输是怎么样实现的。 先分析s3cmci_prepare_pio(host, cmd->data) 根据rw来判断是读还是写 if (rw) { do_pio_write(host); /* Determines SDI generate an interrupt if Tx FIFO fills half*/ enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); } else { enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST); } 如 果是写数据到SD的话,会调用do_pio_write,往FIFO中填充数据。当64字节的FIFO少于33字节时就会产生中断。如果是从SD读数据, 则先使能中断,当FIFO多于31字节时时,则会调用中断服务程序,中断服务程序中将会调用do_pio_read FIFO的数据读出。 接下来分析do_pio_write: to_ptr = host->base + host->sdidata; fifo_free(host)用来检测fifo剩余空间 while ((fifo = fifo_free(host)) > 3) { if (!host->pio_bytes) { res = get_data_buffer(host, &host->pio_bytes, /* If we have reached the end of the block, we have to * write exactly the remaining number of bytes. If we * in the middle of the block, we have to write full * words, so round down to an even multiple of 4. */ if (fifo >= host->pio_bytes)//fifo的空间比pio_bytes大,表明这是读这个块的最后一次 fifo = host->pio_bytes; /* because the volume of FIFO can contain the remaning block*/ else fifo -= fifo & 3;/*round down to an even multiple of 4*/ host->pio_bytes -= fifo;//更新还剩余的没有写完的字 host->pio_count += fifo;/*chang the value of pio_bytes*/ fifo = (fifo + 3) >> 2;//将字节数转化为字数 /*how many words fifo contain,every time we just writ one word*/ ptr = host->pio_ptr; while (fifo--) writel(*ptr++, to_ptr);//写往FIFO. host->pio_ptr = ptr; } 注释一:注意,MMC核心为mrq->data成员分配了一个struct scatterlist的表,用来支持分散聚集,使用这种方法,这样使物理上不一致的内存页,被组装成一个连续的数组,避免了分配大的缓冲区的问题 我们看代码 if (host->pio_sgptr >= host->mrq->data->sg_len) { dbg(host, dbg_debug, "no more buffers (%i/%i) ", host->pio_sgptr, host->mrq->data->sg_len); return -EBUSY; } sg = &host->mrq->data->sg[host->pio_sgptr]; *bytes = sg->length;//页缓冲区中的长度 *pointer = sg_virt(sg);将页地址映射为虚拟地址 host->pio_sgptr++;这里表明我们的程序又完成了一次映射 这样,每一个mmc请求,我们只能处理scatterlist表中的一个页(块)。因此,完成一次完整的请求需要映射sg_len次 再来总结一下一个mmc写设备请求的过程: 在 s3cmci_prepare_pio中我们第一次先调用do_pio_write,如果FIFO空间大于3,且能够获取到scatterlist,则我 们就开始往FIFO写数据,当FIFO空间小于3,则使能TXFIFOHALF中断,在中断服务程序中,如果检测到TFDET表明又有FIFO空间了,则 关闭TXFIFOHALF中断,并调用do_pio_write进行写。 数据流向如下:scatterlist-------->fifo---------->sdcard 一个mmc读设备请求的过程数据流向如下:sdcard --------> fifo ---------->scatterlist, ???? 关于读数据的过程,中断的触发不是很清楚,s3cmci_prepare_pio中enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF,S3C2410_SDIIMSK_RXFIFOLAST);但如果没从SD卡中读数据,怎么会引 发这个中断呢?是由S3C2410_SDIIMSK_RXFIFOLAST引起的吗 接下来我们分析一下中断服务程序: static irqreturn_t s3cmci_irq(int irq, void *dev_id) 该程序先获取所有的状态寄存器: mci_csta = readl(host->base + S3C2410_SDICMDSTAT); mci_dsta = readl(host->base + S3C2410_SDIDSTA); mci_dcnt = readl(host->base + S3C2410_SDIDCNT); mci_fsta = readl(host->base + S3C2410_SDIFSTA); mci_imsk = readl(host->base + host->sdiimsk); 这些将作为中断处理的依据。 如果不是DMA模式,则处理数据的收发 if (!host->dodma) { if ((host->pio_active == XFER_WRITE) && (mci_fsta & S3C2410_SDIFSTA_TFDET)) { /*This bit indicates that FIFO data is available for transmit when DatMode is data transmit mode. If DMA mode is enable, sd host requests DMA operation.*/ disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); tasklet_schedule(&host->pio_tasklet); 注意我们采用tasklet这种延时机制来减少中断服务的时间,延时函数pio_tasklet中调用了do_pio_write和了do_pio_read host->status = "pio tx"; } if ((host->pio_active == XFER_READ) && (mci_fsta & S3C2410_SDIFSTA_RFDET)) { disable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF | S3C2410_SDIIMSK_RXFIFOLAST); tasklet_schedule(&host->pio_tasklet); host->status = "pio rx"; } 接下来的很多代码是对其他的一些类型中断的处理。 最后来分析DMA模式:这种模式下不需要CPU的干预。S3C2440的DMA有4个通道,我们选择了通道0 static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data) { int dma_len, i; int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0; BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR); s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW);//注一 s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE);//注二 if (dma_len == 0) return -ENOMEM; host->dma_complete = 0; host->dmatogo = dma_len; for (i = 0; i sg), sg_dma_len(&data->sg)); res = s3c2410_dma_enqueue(host->dma, (void *) host, sg_dma_address(&data->sg), sg_dma_len(&data->sg)); if (res) { s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH); return -EBUSY; } } s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START); return 0; } 注 一:这个函数先调用s3c2410_dma_devconfig来配置DMA 源/目的的意见类型和地址,注意我们这里的设备地址host->mem->start + host->sdidata实际上就是SDIDATA寄存器的地址值,如果是写SD卡,则为目的地址,否则为源地址。然后调用 s3c2410_dma_set_buffdone_fn(host->dma, s3cmci_dma_done_callback); 设置dma通道0的回调函数。 注二: dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); 这里进行分散/聚集映射(P444,LDD3),返回值是传送的DMA缓冲区数,可能会小于sg_len,也就是说sg_len与dma_len可能是不同的。 sg_dma_address(&data->sg),返回的是总线(DMA)地址 sg_dma_len(&data->sg)); 返回的是缓冲区的长度。 最后调用s3c2410_dma_enqueue(host->dma, (void *) host, sg_dma_address(&data->sg), sg_dma_len(&data->sg)); 对每个DMA缓冲区进行排队,等待处理。 s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);启动DMA 这样DMA缓冲区就和scatterlist联系起来,当写数据时,scatterlist中的数据由于上面的映射关系会直接“拷贝”到DMA缓冲区,当读数据时则反之。整个过程不需要CPU干预,自动完成。 4、CORE层分析: CORE层完成了不同协议和规范的实现,并为HOST层的驱动提供了接口函数,在HOST层我们曾经调用的两个函数: mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev); mmc_add_host(mmc); 我们就从这两个函数入手,来分析CORE层与HOST层是如何交互的。 先看mmc_alloc_host函数: dev_set_name(&host->class_dev, "mmc%d", host->index); host->parent = dev; host->class_dev.parent = dev; host->class_dev.class = &mmc_host_class; device_initialize(&host->class_dev); 这几句是将导致在/SYS/CLASS/mmc_host下出现mmc0目录,添加类设备,在2.6.21后的版本中,类设备的class_device已近被device所取代,LDD3P387的内容有点OUT了 INIT_DELAYED_WORK(&host->detect, mmc_rescan); 初始化了一个工作队列,延时函数为mmc_rescan,这个延时函数很重要,下午要详细分析 最后对host做一些默认配置,不过这些配置在probe函数的后面都被重置了。 分析mmc_add_host(mmc); device_add(&host->class_dev);这里才真正的添加了类设备。 其中调用了mmc_start_host void mmc_start_host(struct mmc_host *host) { mmc_power_off(host); mmc_detect_change(host, 0); } mmc_power_off中对ios进行了设置,然后调用mmc_set_ios(host); host->ios.power_mode = MMC_POWER_OFF; host->ios.bus_width = MMC_BUS_WIDTH_1; host->ios.timing = MMC_TIMING_LEGACY; mmc_set_ios(host); mmc_set_ios(host)中的关键语句host->ops->set_ios(host, ios);这里的set_ios实际上就是我们前面所提到的.set_ios = s3cmci_set_ios, 再看mmc_detect_change(host, 0);最后一句是 mmc_schedule_delayed_work(&host->detect, delay); 实际上就是调用我们前面说的延时函数mmc_rescan mmc_power_up(host);//这个函数实际上与前面的mmc_power_off类似,不过设置了启动时需要的ios mmc_go_idle(host); //CMD0 ,from inactive to idle mmc_send_if_cond(host, host->ocr_avail);//发送SD_SEND_IF_COND,是使用SD2.0卡才需要设置的命令 /*suppot for 2.0 card*/ * ...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; } 蓝色部分是遵照SD卡协议的SD卡启动过程,包括了非激活模式、卡识别模式和数据传输模式三种模式共九种状态的转换,你需要参照相关规范来理解。可以先参考下面三章图对模式和状态,以及状态转换有个初步了解。 我们最初的SD卡的状态时inactive状态调用mmc_go_idle(host)后,发送命令CMD0是其处于IDLE状态。 我们详细分析一下mmc_go_idle memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = MMC_GO_IDLE_STATE; MMC_GO_IDLE_STATE就是命令CMD0 cmd.arg = 0;此命令无参数 cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC; err = mmc_wait_for_cmd(host, &cmd, 0);//见注1 mmc_delay(1); 注1:mmc_wait_for_cmd(host, &cmd, 0)是用来发送命令的,我们揭开它的神秘面纱吧。 memset(&mrq, 0, sizeof(struct mmc_request)); memset(cmd->resp, 0, sizeof(cmd->resp)); cmd->retries = retries; mrq.cmd = cmd;将命令嵌入到一个mmc请求中 cmd->data = NULL;mmc命令的data部分设置为NULL,这样表示我们要传输的是命令而不是数据 mmc_wait_for_req(host, &mrq);//关键部分 在该函数中调用了mmc_start_request,而这个函数调用了host->ops->request(host, mrq),这个request函数就是我们在前面分析的s3cmci_request,这样MMC核心第二次核HOST层握手了 我们再看看: err = mmc_send_app_op_cond(host, 0, &ocr);//注一 if (!err) { if (mmc_attach_sd(host, ocr))//注二 mmc_power_off(host); goto out; 注 一:实际上是要发送ACMD41命令,这条命令可以用来获取SDcard的允许电压范围值,由于这是一条应用命令,所有发送它之前需要发送CMD_55命 令。执行完后card状态变为READY获取的电压范围保存在ocr中,再调用mmc_attach_sd(host, ocr)看这个电压范围是否满足主机的要求,不满足,则power_off主机。 注二:mmc_attach_sd完成匹配,和初始化卡的功能 host->ocr = mmc_select_voltage(host, ocr);看是否匹配,如果匹配则做下面初始化工作 mmc_sd_init_card(host, host->ocr, NULL);我们分析该函数 (1)mmc_all_send_cid()这个函数发生CMD2,获取卡的身份信息,进入到身份状态 (2)card = mmc_alloc_card(host, &sd_type);分配一张SD类型的card结构 (3)接着调用mmc_send_relative_add,获取卡的相对地址,注意一前卡和主机通信都采用默认地址,现在有了自己的地址了,进入到stand_by状态 (4)通过发送SEND_CSD (CMD9) 获取CSD 寄存器的信息,包括block长度,卡容量等信息 (5) mmc_select_card(card)发送CMD7,选中目前RADD地址上的卡,任何时候总线上只有一张卡被选中,进入了传输状态, (6)调用mmc_app_send_scr发送命令ACMD51获取SRC寄存器的内容,进入到SENDING-DATA状态 在函数中还将获得的各个卡寄存器的内容解码,并保存到cmd结构的相应成员中。 (7)if (host->ops->get_ro(host) > 0) mmc_card_set_readonly(card); 通过调用get_ro(host)函数,实际上就是s3cmci_get_ro函数了。我们判断是否写保护,如果是的,将card状态设置为只读状态 最后再mmc_attach_sd里,我们将card结构添加进去 mmc_add_card(host->card); dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca);这里我们以host名+rca地址来命名卡我们可以看到在/sys/devices/platform/s3c2440- sdi/mmc_host:mmc0/下出现mmc0:0002的目录,这个0002就是rca地址 到这里我们分析完了MMC的核心层。 4: CARD层分析: 因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的SD卡如何实现为块设备的。先看block.C中的probe函数 MMC块设备用如下结构表示: struct mmc_blk_data { spinlock_t lock; struct gendisk *disk; struct mmc_queue queue; unsigned int usage; unsigned int read_only; }; 我们先看mmc_blk_alloc( ) devidx = find_first_zero_bit(dev_use, MMC_NUM_MINORS); if (devidx >= MMC_NUM_MINORS)//这表明我们的mmc层对多支持16个card,每个card占8分区 return ERR_PTR(-ENOSPC); __set_bit(devidx, dev_use); md->disk = alloc_disk(1 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 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);//这个名字将在/proc/device下出现 我们可以看到在/sys/block下有个"mmcblk0 blk_queue_hardsect_size(md->queue.queue, 512);//设置硬件扇区的容量 } 注一: mq->queue = blk_init_queue(mmc_request, lock);初始化将request函数与队列绑定 if (!mq->queue) return -ENOMEM; mq->queue->queuedata = mq; mq->req = NULL; blk_queue_prep_rq(mq->queue, mmc_prep_request); //命令预处理,为驱动程序在返回evl_next_request之前,提供检查和预处理请求的机制,详细见LDD3 P485 //command prepare process blk_queue_ordered(mq->queue, QUEUE_ORDERED_DRAIN, NULL);// //barrier request屏障请求,防止重新组合产生的错误,设置标准后,保证请求的数据及时写入到介质。 mq->sg = kmalloc(sizeof(struct scatterlist) * host->max_phys_segs, GFP_KERNEL); if (!mq->sg) { ret = -ENOMEM; goto cleanup_queue; } sg_init_table(mq->sg, host->max_phys_segs); } //分配scatterlist结构体 mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd");最后设置了一个内核线程,线程关联的函数是mmc_queue_thread,这个很重要,我们待会分析。 接下来调用mmc_blk_set_blksize来设置block的长度为512。 一切都准备好了以后激活磁盘:add_disk(md->disk); 最后来分析request函数: * * Generic MMC request handler. This is called for any queue on a * particular host. When the host is not busy, we look for a request * on any queue on this host, and attempt to issue it. This may * not be the queue we were asked to process.也就是说,elv_next_request返回来的 req不一定是mq->req */ static void mmc_request(struct request_queue *q) { struct mmc_queue *mq = q->queuedata; struct request *req; int ret; if (!mq) { printk(KERN_ERR "MMC: killing requests for dead queue "); while ((req = elv_next_request(q)) != NULL) { do { ret = __blk_end_request(req, -EIO, blk_rq_cur_bytes(req));//没有可以处理的请求,则就素这个请求 } while (ret); } return; } if (!mq->req) wake_up_process(mq->thread);//注一 } 注一:我们发现,与LDD3中介绍的块设备编程方法不同,并没有出来任何与bio结构相关的东西,当请求获取后,我们通过什么来进行数据块的传输呢,这里就是通过唤醒mq->thread线程来实现的,这个线程实际上就是mmc_queue_thread函数 static int mmc_queue_thread(void *d) { struct mmc_queue *mq = d; struct request_queue *q = mq->queue; current->flags |= PF_MEMALLOC; down(&mq->thread_sem); do { struct request *req = NULL; spin_lock_irq(q->queue_lock); set_current_state(TASK_INTERRUPTIBLE); if (!blk_queue_plugged(q)) req = elv_next_request(q); mq->req = req; spin_unlock_irq(q->queue_lock); if (!req) { if (kthread_should_stop()) { set_current_state(TASK_RUNNING); break; } up(&mq->thread_sem); schedule(); down(&mq->thread_sem); continue; } set_current_state(TASK_RUNNING); //蓝色部分不是很理解,大概的意思应该还是获取一个可处理的请求 mq->issue_fn(mq, req);//注一 } while (1); up(&mq->thread_sem); return 0; } 注一:我们看看issue_fn函数做了些什么,这个函数相当复杂 我们看关键的部分: brq.data.sg = mq->sg; brq.data.sg_len = mmc_queue_map_sg(mq); /* * Adjust the sg list so it is the same size as the * request. */ if (brq.data.blocks != req->nr_sectors) { int i, data_size = brq.data.blocks length; if (data_size length += data_size; i++; break; } } brq.data.sg_len = i; } 以上这些代码用来准备scatterlist,这是数据传输的缓冲区 mmc_wait_for_req(card->host, &brq.mrq);接下来我们向host发送请求,这个函数应该很熟悉了,它的最后一句就是 host->ops->request(host, mrq),这样就和我们驱动程序的request联系起来了,由于这次cmd—>data成员不再为空,所以启动的是数据传输了。 5 实验: 将默认的平台信息作了更改,这样 .get_ro = s3cmci_get_ro, .get_cd = s3cmci_card_present, 两个函数就有实际的作用了 static struct s3c24xx_mci_pdata s3cmci_def_pdata = { /* This is currently here to avoid a number of if (host->pdata) * checks. Any zero fields to ensure reaonable defaults are picked. */ .detect_invert=0, .wprotect_invert=1, .gpio_detect=1, .gpio_wprotect = 1 , }; 不过还有一点不清楚的是, host_dodma设置为1的时候,在/sdcard 下找不到任何东西 /proc/devices中也查找不到相应的设备 从打印的信息看: 7>mmc0: clock 0Hz busmode 1 powermode 1 cs 0 Vdd 21 width 0 timing 0 s3c2440-sdi s3c2440-sdi: running at 0kHz (requested: 0kHz). mmc0: clock 197753Hz busmode 1 powermode 2 cs 0 Vdd 21 width 0 timing 0 s3c2440-sdi s3c2440-sdi: running at 198kHz (requested: 197kHz). mmc0: clock 197753Hz busmode 1 powermode 2 cs 1 Vdd 21 width 0 timing 0 s3c2440-sdi s3c2440-sdi: running at 198kHz (requested: 197kHz). mmc0: starting CMD0 arg 00000000 flags 000000c0 s3c2440-sdi s3c2440-sdi: CMD[OK] #1 op:0 arg:0x00000000 flags:0x08c0 retries:0 R0:0x00000000 mmc0: req done (CMD0): 0: 00000000 00000000 00000000 00000000 发送命令基本都是成功的,为什么会这样??? 6结论:到此为止,按照数据和命令流的方向,我们分析了MMC子系统的基本结构,很多细节的地方还不是很清楚,不过至少为写驱动程序做了相应的准备了。 |