引言
前一段时间,我们分别在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