OpenRisc-51-基于orpsoc,linux的sd card controller的驱动程序

本文档详细介绍了如何在OpenRisc的ORPSoC平台下,基于Linux开发SD卡控制器的驱动程序,包括基本思想、工作机制、代码实现、验证过程以及Linux配置的附录。驱动程序利用中断处理程序的上半部和下半部,采用completion机制进行同步,并通过块设备框架实现文件系统支持。
摘要由CSDN通过智能技术生成

引言

前一段时间,我们分别在eCos和linux上实现了ORPSoC中的sd卡控制器的基本操作。在eCos上实现了完整的,支持文件系统的driver,在linux上,也实现了基本的IO操作,但是还没有实现基于文件系统的操作,本小节就解决这个问题。

关于之前我们所做的工作,请参考:

OpenRisc-30-SD card controller模块分析与验证:http://blog.csdn.net/rill_zhen/article/details/9111213

OpenRisc-35-基于orpsoc,eCos的sd card controller的测试实验:http://blog.csdn.net/rill_zhen/article/details/9365477

OpenRisc-38-基于orpsoc,linux的sd card controller的测试实验:http://blog.csdn.net/rill_zhen/article/details/9419651


1,基本思想

前面的基于linux的工作实现了基本的SD卡的IO,通过之前的努力,我们已经可以对SD卡进行读写操作,但是这个读写操作只能基于block,不能以文件的形式操作SD卡。

所以为了实现以文件的操作,需要文件系统的支持。

为了提高传输速度,驱动中仔细设计了中断处理程序的上半部分和下半部分。

一般情况下,上半部分和下半部分通过信号量来进行同步,我们这次使用了两外一种机制,就是completion。这种机制主要通过complete和wait_for_completion两个函数实现。

Completion是一种轻量级的机制,他允许一个线程告诉另一个线程某个工作已经完成。

此外,还需要注意的是,本驱动并没有使用linux专门为SD卡提供的framework,而是采用标准的块设备的framework。这需要重新配置linux的编译选项,如何配置内核,请参考附录部分。


2,工作机制

为了便于读者更容易理解,这里简单介绍一下本驱动的工作机制。

1,本驱动是以model的形式编译的,编译会生成ko文件,通过insmod命令加载到内核,并会生成/dev/sdcmsc的设备节点。

2,在模块初始化部分,会对sd卡进行初始化,并注册块设备驱动,注册中断处理程序。关于中断的使用,如中断号的确定,我们之前介绍过,请参考 http://blog.csdn.net/rill_zhen/article/details/8894856

3,通过mount -t vfat /dev/sdcmmc /sdc 来挂载SD卡,其中/sdc是通过mkdir命令创建的挂载点。

4,一旦挂载成功,我们就可以使用cd ls cat cp mv rm 等命令来对SD卡进行操作了。

5,在linux进行IO操作时,首先会启动DMA传输过程,然后会唤醒一个名字为sdcmsc_queue_thread的内核线程,这个内核线程会等待DMA传输完毕之后的中断信号。一旦DMA传输完毕,中断处理程序会告诉这个内核线程。这样做的好处是,在进行DMA传输时,驱动程序会通过schedule()让出CPU,减少了CPU的占用时间。


3,编码

了解了本驱动的基本思想和大体的工作机制之后,就可以开始编码了,这里直接给出代码清单。

这里需要说明的是,其实,一切细节都可以从代码中获得,所以还是建议读者在使用前仔细阅读本代码。

code list:


1>sdcmsc.c


/*
* rill create at 2013-09-18
* rillzhen@gmail.com
*/


#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/sched.h>
#include <linux/kernel.h>	/* printk() */
#include <linux/slab.h>		/* kmalloc() */
#include <linux/fs.h>		/* everything... */
#include <linux/errno.h>	/* error codes */
#include <linux/timer.h>
#include <linux/types.h>	/* size_t */
#include <linux/fcntl.h>	/* O_ACCMODE */
#include <linux/hdreg.h>	/* HDIO_GETGEO */
#include <linux/kdev_t.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h>	/* invalidate_bdev */
#include <linux/bio.h>
#include <linux/delay.h>
#include <linux/completion.h>
#include <linux/kthread.h>
#include <linux/interrupt.h>
#include "sdcmsc.h"
MODULE_LICENSE("Dual BSD/GPL");

static struct sdcmsc_card* sdcmsc_dev_p = NULL;
static DEFINE_MUTEX(block_mutex);

static DEFINE_MUTEX(open_lock);
DECLARE_COMPLETION(comp_dma);

static struct sdcmsc_card* sdcmsc_blk_get(struct gendisk *disk)
{
	struct sdcmsc_card* sdev;

	mutex_lock(&open_lock);
	sdev = disk->private_data;
	if(sdev && sdev->usage ==0)
		sdev = NULL;
	if(sdev)
		sdev->usage++;
	mutex_unlock(&open_lock);

	return sdev;
}
static void sdcmsc_blk_put(struct sdcmsc_card *dev)
{
	mutex_lock(&open_lock);
	dev->usage--;
	if(dev->usage == 0)
	{
		blk_cleanup_queue(dev->mq.queue);
		put_disk(dev->gd);
		kfree(dev);
	}
	mutex_unlock(&open_lock);
}
static irqreturn_t sdcmsc_irq_dat(int irq, void *dev_id)
{
	struct sdcmsc_card * dev= dev_id;
	void __iomem * sdcmsc_base = dev->sdcmsc_base;
	unsigned reg;
//	printk(KERN_ALERT "I'm in interrupt!");
	reg = ioread32(sdcmsc_base + SDCMSC_DAT_INT_STATUS);
	reg = cpu_to_le32(reg);
	iowrite32(0, sdcmsc_base + SDCMSC_DAT_INT_STATUS);
	//Check for errors
	if(reg !=  SDCMSC_DAT_INT_STATUS_TRS)
	{
		if(reg & (1 << 5)) printk(KERN_ALERT "Transmission error\n");
		if(reg & (1 << 4)) printk(KERN_ALERT "Command error\n");
		if(reg & (1 << 2)) printk(KERN_ALERT "FIFO error\n");
		if(reg & (1 << 1)) printk(KERN_ALERT "Retry error\n");
	}
//	printk(KERN_ALERT "before complete!");
	complete(&comp_dma);
	return IRQ_HANDLED;

}

static int sdcmsc_card_queue(struct sdcmsc_card* dev, int direction_transmit,
	unsigned long block_addr, unsigned long  buffer_addr)
{
	unsigned long reg;
	//int i;
	void __iomem *sdcmsc_base = dev->sdcmsc_base;
//	printk(KERN_ALERT "sdcmsc_base:0x%x\n", sdcmsc_base);
//	printk(KERN_ALERT "block_addr:%d\n", block_addr-8192);
	if(direction_transmit) {
//		printk(KERN_ALERT "I'm in writing");
		reg = le32_to_cpu(buffer_addr);
		iowrite32(reg, sdcmsc_base + SDCMSC_BD_TX);
		block_addr = le32_to_cpu(block_addr);
		iowrite32(block_addr, sdcmsc_base + SDCMSC_BD_TX);
	}
	else
	{
//		printk(KERN_ALERT "I'm in Reading\n");
//		printk(KERN_ALERT "buffer_addr:0x%x\n", buffer_addr);
		reg= le32_to_cpu(buffer_addr);
		iowrite32(reg, sdcmsc_base + SDCMSC_BD_RX);
//		printk(KERN_ALERT "block_addr:%d\n", block_addr);
		block_addr = le32_to_cpu(block_addr);
		iowrite32(block_addr, sdcmsc_base + SDCMSC_BD_RX);
	}
//	printk(KERN_ALERT "before wait_for_completion");
	wait_for_completion(&comp_dma);
//	printk(KERN_ALERT "after wait_for_completion");
	return 1;
}
static int sdcmsc_disk_read(struct sdcmsc_card* dev, unsigned long  buf, 
	unsigned long blocks, unsigned long first_block){
	void __iomem *sdcmsc_base =dev->sdcmsc_base;

	int i;
	int result;
	unsigned int reg;
//	printk(KERN_ALERT "buf addr:0x%x\n", buf);
//	printk(KERN_ALERT "block num:%d\n", blocks);
//	printk(KERN_ALERT "first_block:%d\n", first_block);
	for(i=0; i < blocks; i++) {
		//Check for free receive buffers
		reg= ioread32(sdcmsc_base + SDCMSC_BD_BUFFER_STATUS);
		reg = cpu_to_le32(reg);
		reg >>= 8;
		reg &= 0xFF;
		if(reg == 0) {
			printk(KERN_ALERT "IO ERROR \n");
			return -1;
		}
//		result = sdcmsc_card_queue(dev, 0, first_block, buf );
		result = sdcmsc_card_queue(dev, 0, first_block+i, buf+i*512 );
		if(!result){
			printk(KERN_ALERT "IO ERROR \n");
			return -1;
		}
	}
	return 1;
}

static int sdcmsc_disk_write(struct sdcmsc_card* dev, unsigned long  buf, 
	unsigned long blocks, unsigned long first_block){
	void __iomem *sdcmsc_base = dev->sdcmsc_base;

	int i;
	int result;
	unsigned int reg;

	for(i = 0; i < blocks;i++) {

		//Check for free transmit buffers
		reg = ioread32(sdcmsc_base + SDCMSC_BD_BUFFER_STATUS);
		reg = cpu_to_le32(reg);
		reg &= 0xFF;
		if(reg == 0) {
			printk(KERN_ALERT "IO ERROR \n");
			return -1;
		}
		//result = sdcmsc_card_queue(dev, 1, first_block, buf );
		result = sdcmsc_card_queue(dev, 1, first_block+i, buf + i*512 );
		if(!result){
			printk(KERN_ALERT "IO ERROR \n");
			return -1;
		}
	}
	return 1;	
}
static void sdcmsc_transfer(struct sdcmsc_card *dev, unsigned long sector,
	unsigned long nsect, unsigned long buffer, int write)
{
	if(write)
		sdcmsc_disk_write(dev, buffer, nsect, sector);
	else
		sdcmsc_disk_read(dev, buffer, nsect, sector);
}

static int sdcmsc_ioctl(struct block_device *bdev, fmode_t mode,
	unsigned int  cmd, unsigned long arg)
{
	return 1;
}
static int sdcmsc_open(struct block_device *bdev, fmode_t mode)
{
//	printk(KERN_ALERT "I'm in sdcmsc_open\n");
	struct sdcmsc_card* dev= sdcmsc_blk_get(bdev->bd_disk);
	int ret = -ENXIO;

	mutex_lock(&block_mutex);
	if(dev){
		if(dev->usage == 2)
			check_disk_change(bdev);
		ret = 0;
	}
	mutex_unlock(&block_mutex);
//	printk(KERN_ALERT "sdcmsc_open ok!\n");
	return ret;
}

static int sdcmsc_release(struct gendisk *disk, fmode_t mode)
{
	struct sdcmsc_card* dev =  disk->private_data;

	mutex_lock(&block_mutex);
	sdcmsc_blk_put(dev);
	mutex_unlock(&block_mutex);
	return 0;
}
/*unsigned long bytes_to_sectors_checked(unsigned long size_bytes)
{
	if(size_bytes%512)
	{
		printk(KERN_ALERT "What's the fuck!\n************");
	}
	return size_bytes/512;
}*/
static int sdcmsc_xfer_bio(struct sdcmsc_card* dev, struct bio* bio)
{
	int i;
	unsigned long tmp=0;
	struct bio_vec *bvec;
	struct page *bv_page;
	unsigned long phy_addr;
//	int bvecNum =1;
	sector_t sector = bio->bi_sector;
	sector += 8192; 
	/* Do each segment independently. */
	bio_for_each_segment(bvec, bio, i){
//		printk(KERN_ALERT "bvecNum:%d", bvecNum++);
		//char * buffer = __bio_kmap_atomic(bio, i, KM_USER0);
//		bv_page = bio_page(bio);
		bv_page = bvec->bv_page;
		phy_addr = page_to_phys(bv_page);
		phy_addr += bvec->bv_offset;
//		printk(KERN_ALERT "phy_addr:0x%x\n", phy_addr);
//		tmp=bytes_to_sectors_checked(bio_cur_bytes(bio));
//		printk(KERN_ALERT "sector num:%d\n", tmp);
//		printk(KERN_ALERT "bv_len:%d\n", bvec->bv_len>>9);
		tmp = bvec->bv_len>>9;
//		printk(KERN_ALERT "direction:%d\n",bio_data_dir(bio)==WRITE);
		sdcmsc_transfer(dev, sector, /*bytes_to_sectors_checked(bio_cur_bytes(bio))*/tmp, 
			phy_addr, bio_data_dir(bio) == WRITE);
		sector += /*(bytes_to_sectors_checked(bio_cur_bytes(bio)))*/tmp;
		//int j;
		/*for(j=0; j<512;j++)
		{	printk(KERN_ALERT "%.2x\t", buffer[j]);
		}*/
		//__bio_kunmap_atomic(bio, KM_USER0);
	}
	return 0;
}

static int sdcmsc_xfer_request(struct sdcmsc_card* dev, struct request *req)
{
	struct bio *bio;
	unsigned long  bytes_tranf=0;
//	int bioNum =1;
	__rq_for_each_bio(bio,req){
//		printk(KERN_ALERT "bioNum:%d", bioNum++);
		sdcmsc_xfer_bio(dev, bio);
		bytes_tranf +=bio->bi_size;
	}
	return bytes_tranf;
}
static int sdcmsc_blk_issue_rq(struct mmc_queue *mq, struct request *req)
{
	struct sdcmsc_card * dev = mq->data;
	unsigned long bytes_tranf =0;
	int ret =0;
	if(req)
	{
		if(req->cmd_type != REQ_TYPE_FS){
			printk(KERN_ALERT "Skip non-fs request\n");
			ret= -EIO;
		
		}else
		{
			bytes_tranf=sdcmsc_xfer_request(dev, req);
		}
	//__blk_end_request(req, 0,bytes_tranf);
		spin_lock_irq(&dev->lock);
	//	__blk_end_request_cur(req, ret);
		__blk_end_request(req, 0,bytes_tranf);
		spin_unlock_irq(&dev->lock);
	}
	return 0;
}

static int sdcmsc_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);
		req = blk_fetch_request(q);
		mq->mqrq_cur = req;
		spin_unlock_irq(q->queue_lock);

		if(req || mq->mqrq_prev){
			set_current_state(TASK_RUNNING);
//			printk(KERN_ALERT "before issue_fn");
			mq->issue_fn(mq, req);
		} else {
//	printk(	KERN_ALERT "I will sleep or stop");
			if(kthread_should_stop()){
//				printk(KERN_ALERT "I will stop");
				set_current_state(TASK_RUNNING);
				break;
			}
			up(&mq->thread_sem);
			schedule();
//			printk(KERN_ALERT "after schedule");
			down(&mq->thread_sem);
		}

		mq->mqrq_prev = mq->mqrq_cur;
		mq->mqrq_cur = NULL;
	}while(1);

	up(&mq->thread_sem);

	return 0;
}

static void sdcmsc_request(struct request_queue *q)
{
	struct mmc_queue *mq = q->queuedata;
	struct request *req;

	if(!mq) {
		while((req = blk_fetch_request(q)) != NULL) {
			req->cmd_flags |= REQ_QUIET;
			__blk_end_request_all(req, -EIO);
		}
		return;
	}
//	printk(KERN_ALERT "before wake_up_process");
	if(!mq->mqrq_cur && !mq->mqrq_prev)
	{
//		printk(KERN_ALERT "I will wake up process");
		wake_up_process(mq->thread);
	}
}

static int sdcmsc_card_cmd(void __iomem* sdcmsc_base,unsigned cmd, unsigned arg, 
				unsigned *response){
	// Send command to card
	cmd = le32_to_cpu(cmd);
	iowrite32(cmd, sdcmsc_base + SDCMSC_COMMAND);
	arg = le32_to_cpu(arg);
	iowrite32(arg, sdcmsc_base + SDCMSC_ARGUMENT);

	// Wait for response
	unsigned reg;
	unsigned mask = SDCMSC_NORMAL_INT_STATUS_EI | SDCMSC_NORMAL_INT_STATUS_CC;

	do {
	reg = ioread32(sdcmsc_base + SDCMSC_NORMAL_INT_STATUS);
	reg = cpu_to_le32(reg);
	} while(!(reg&mask));
	iowrite32(0, sdcmsc_base + SDCMSC_NORMAL_INT_STATUS);

	//Optionally read response register
	if(response) {
		unsigned tmp;
		tmp = ioread32(sdcmsc_base + SDCMSC_RESPONSE);
		tmp = cpu_to_le32(tmp);
		*response =tmp;
	}
	
	// Check for errors
	if(reg & SDCMSC_NORMAL_INT_STATUS_EI) {
		reg = ioread32(sdcmsc_base + SDCMSC_ERROR_INT_STATUS);
		reg = cpu_to_le32(reg);
		if(reg & (1 << 3)) printk(KERN_ALERT "Command index error\n");
		if(reg & (1 << 1)) printk(KERN_ALERT "Command CRC error\n");
		if(reg & (1 << 0)) printk(KERN_ALERT "Command timeout\n");
		iowrite32(0, sdcmsc_base+ SDCMSC_ERROR_INT_STATUS);
		return 0;
	}
	else{
		return 1;
	}
}

int sdcmsc_dev_probe(void __iomem * sdcmsc_base)
{
	
	unsigned cmd;
	unsigned reg;
	unsigned arg;
	unsigned card_capacity;
	unsigned int param;
	
	// Set highest possible timeout
	param = le32_to_cpu(0xFFFE);
	iowrite32(param,sdcmsc_base + SDCMSC_TIMEOUT);	

	//Reset the peripheral
	param = le32_to_cpu(1);
	iowrite32(param, sdcmsc_base + SDCMSC_SOFTWARE_RESET);
	param = le32_to_cpu(2);
	iowrite32(param, sdcmsc_base + SDCMSC_CLOCK_DIVIDER);
	param = le32_to_cpu(0);
	iowrite32(param, sdcmsc_base + SDCMSC_SOFTWARE_RESET);

	//Send CMD0 to switch the card to idle state
	cmd = SDCMSC_COMMAND_CMDI(0);
	if(!sdcmsc_card_cmd(sdcmsc_base,cmd, 0, NULL)) return 0;

	// Send CMD8 offering 2.7V to 3.6V range
	// If the card doesn't re
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值