Linux下讀寫FLASH驅動——MTD設備分析

最近在學習驅動讀寫flash的代碼部分。經歷了可笑的過程:開始我知道flash用通過spi口來讀寫。所以就到了driver/spi 下面看相關代碼。發現有個spidLinux下讀寫FLASH驅動——MTD設備分析ev.c裏面有read/write/ioctl等函數。而且還有一個davinci_spi_master.以爲調用spi驅動的時候會首先調用到這裏,於是就想怎麼在上層應用裏將spidev.c裏open調用到就可以了。最後修改了一些地方就在應用的地方打開了這個字符設備驅動。在dev下面生成了dev/spidev0.0目錄。於是打開它還調用到了spidev.c裏的相關函數。甚是竊喜,但突然發現這個跟我要讀寫的flash又有什麼關係呢?flash芯片型號是:m25p40。知道了又怎麼樣?我該如何讀寫它呢。後來突然在網上看了這麼一段話MTD(memory technology device內存技術設備)是用於訪問memory設備(ROM、flash)的Linux的子系統。MTD的主要目的是爲了使新的memory設備的驅動更加簡單,爲此它在硬件和上層之間提供了一個抽象的接口。MTD的所有源代碼在/drivers/mtd子目錄下。我將CFI接口的MTD設備分爲四層(從設備節點直到底層硬件驅動),這四層從上到下依次是:設備節點、MTD設備層、MTD原始設備層和硬件驅動層。(http://blog.csdn.net/binghuiliang/archive/2008/01/23/2060794.aspx)鬱悶之餘看到了這樣的信息,發現原來flash不是要註冊一個什麼spi設備,而是要通過mtd設備來讀取後來在dev/mtd/devices文件夾裏看到了m25p80.c這個代碼。打開發現裏面是對這一類flash驅動的支持代碼。天哪怎麼回事,後來就又看了裏面的函數有read/write/probe函數但是沒有發現open函數,甚是煩惱。不知道open函數哪裏去了。是不是probe代替了呢??同時也沒有發現ops這樣的文件操作結構體。問題出現了。


       參看高手說在應用裏要 system("flash_eraseall /dev/mtd4");spi_fd =open("/dev/mtd4",O_RDWR, 0);
這麼調用,而mtd4在哪裏註冊的我就不知道了。現在還在尋找中。read和write都是調用到了m25p80.c裏函數。下面具體說一下如何添加m25p80.c驅動吧。

步驟如下:
1、make menuconfig裏選擇MTD/下相應的選項。內核已經配好了。
2、修改arch/arm/mach-davinci下面的davinci_spi_platform.c 在裏面加入
static struct flash_platform_data davinci_m25P40_info =
{
   .name = "m25p80",
   .parts = NULL,
   .nr_parts = 0,
   .type = "m25p40",
};
static struct spi_board_info dm6467_spi_board_info[] = {
{
   // SPI FLash
   .modalias = "m25p80",
   .platform_data = &davinci_m25P40_info,
   .mode = SPI_MODE_0,
   .irq = 0,
   .max_speed_hz = 4 * 1000 * 1000,
   .bus_num = 0,
   .chip_select = 0, // device number on bus (0-based)
},

};


然後編譯重新編譯內核之後就可以發現在dev目錄下多了一個dev/mtd4設備節點。這裏面有很多奇怪的地方。不知道這個mtd4是怎麼生成的。像那個spidev0.0設備節點是因爲在文件spidev.c裏有賦值給主設備和從設備的地方,而這個在m25p80.c裏並沒有發現任何跡象,只是看到在probe里加進了add_mtd_partitions()函數,還有這樣的語句:
flash->mtd.erase = m25p80_erase;
flash->mtd.read = m25p80_read;
flash->mtd.write = m25p80_write;
然後繼續查找probe最後到了那裏:
static struct spi_driver m25p80_driver = {
.driver = {
   .name = "m25p80",
   .bus = &spi_bus_type,
   .owner = THIS_MODULE,
},
.probe = m25p_probe,
.remove = __devexit_p(m25p_remove),
};
貌似這裏又用probe來註冊了spi_driver結構體。而最後的init和exit函數都用了spi註冊。暈倒!那怎麼最後是open設備mtd4呢。這中間到底發生了什麼?
這裏只能說mtd設備利用了spi總線來達到註冊自己設備的目的。而這個mtd設備在本質上是一個字符設備。
在板子登陸的內核信息裏截獲到以下信息:
call video_register_device() in file videodev.c
call video_register_device() in file videodev.c
call adv7343_initialize()
ad9889_i2c_init() OK!
i2c /dev entries driver
nand_davinci nand_davinci.0: Using soft ECC
info->emifregs = 0xc8008000,EMIF_A1CR = 0x3ffffffc
info->emifregs = 0xc8008000,EMIF_A1CR = 0x88442a8

mtd->writesize(pagesize)=2048
mtd->oobsize=64
mtd->erasesize(blocksize)=0x20000

NAND device: Manufacturer ID: 0xec, Chip ID: 0xf1 (Samsung NAND 128MiB 3,3V 8-bit)
Scanning device for bad blocks
chip_delay = 30
Creating 4 MTD partitions on "nand_davinci.0":
0x00000000-0x000e0000 : "bootloader"
0x000e0000-0x00100000 : "params"
0x00100000-0x004a0000 : "kernel"
0x004a0000-0x08000000 : "filesystem"
nand_davinci nand_davinci.0: hardware revision: 2.2
Enter into m25p_probe
m25p80 spi0.0: m25p40 (512 Kbytes)
dm_spi.0: davinci SPI Controller driver at 0xc8002800 (irq = 43) use_dma=1
pcf8563 0-0051: chip found, driver version 0.4.2
上面的Enter into m25p_probe 是在m25p80.c裏probe函數打印出來的。這麼早就打印了而不是open時候才調用probe是在內核加載時就調用了。
可見這塊板子是用的nandflash並把它分成四部分:bootloader、代碼、內核、文件系統。而m25p80 spi0.0: m25p40 (512 Kbytes)這一句更是
經典,是說生成了m25p80 spi0.0的一個設備m25p40吧,猜的呵呵。

       現在發現mtd字符設備都在mtd/mtdchar.c裏註冊,於是就懷疑是不是open調用到了這裏呢,於是打印驗證後發現open("/dev/mtd4",O_RDWR, 0);調用到mtdchar.c程序裏open函數,那爲什麼read和write卻是調用到m25p80.c呢,這裏的read有沒有調用到呢?這就更奇怪了,是不是mtd自己有一套機制讓打開字符設備的語句直接去打開mtdchar.c的open然後再具體針對某個設備來讀寫。這也不對呀,open返回的是mtd4的設備號啊。
       哈哈,測試了一下原來同樣會調用到mtdchar裏read和write。m25p80.c裏的read和mtdchar.c裏的read函數,這個邏輯是怎麼回事?
       爲了查找這個根據我看了:mtdchar.c裏mtd_read函數,最後終於明白了:
這個函數的基本功能是:


格式:static ssize_t mtd_read(struct file *file, char *buf, size_t count,loff_t *ppos)

功能:MTD字符設備的讀操作

說明:

       當count>0{

              裁減本次操作大小lenmin(MAX_KMALLOC_SIZE,count)

              申請一塊大小爲MAX_KMALLOC_SIZE的內核空間kbuf

              調用mtd_info->readMTD設備中的數據讀入kbuf

              將kbuf中的數據拷貝到用戶空間buf

              count自減

              釋放kbuf

       }

參數:

file:系統給MTD字符設備驅動程序用於傳遞參數的file結構,此函數通過file得到下

層的MTD設備

       buf:用戶空間的指針,用於存放讀取的數據

       count:被讀數據的長度

       ppos:被讀數據在MTD設備中的位置

返回:

       成功:返回實際讀取數據的長度

       失敗:返回錯誤碼

調用:

       mtd_info->read()用於從MTD設備中讀取數據

被調用:

       被註冊進mtd_fops結構

源代碼:
static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
{
struct mtd_file_info *mfi = file->private_data;
struct mtd_info *mtd = mfi->mtd;
size_t retlen=0;
size_t total_retlen=0;
int ret=0;
int len;
char *kbuf;

DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");

if (*ppos + count > mtd->size)
   count = mtd->size - *ppos;

if (!count)
   return 0;

if (count > MAX_KMALLOC_SIZE)
   kbuf=kmalloc(MAX_KMALLOC_SIZE, GFP_KERNEL);
else
   kbuf=kmalloc(count, GFP_KERNEL);

if (!kbuf)
   return -ENOMEM;

   printk("mtd_read called!!000000000000000\n");
while (count) {

   if (count > MAX_KMALLOC_SIZE)
    len = MAX_KMALLOC_SIZE;
   else
    len = count;

   switch (mfi->mode) {
   case MTD_MODE_OTP_FACTORY:
    ret = mtd->read_fact_prot_reg(mtd, *ppos, len, &retlen, kbuf);
    break;
   case MTD_MODE_OTP_USER:
    ret = mtd->read_user_prot_reg(mtd, *ppos, len, &retlen, kbuf);
    break;
   case MTD_MODE_RAW:
   {
    struct mtd_oob_ops ops;

    ops.mode = MTD_OOB_RAW;
    ops.datbuf = kbuf;
    ops.oobbuf = NULL;
    ops.len = len;

    ret = mtd->read_oob(mtd, *ppos, &ops);
    retlen = ops.retlen;
    break;
   }
   default:
    printk("mtd_read called!!111111111111\n");
    ret = mtd->read(mtd, *ppos, len, &retlen, kbuf);
   }
  
   if (!ret || (ret == -EUCLEAN) || (ret == -EBADMSG)) {
    *ppos += retlen;
    if (copy_to_user(buf, kbuf, retlen)) {
     kfree(kbuf);
     return -EFAULT;
    }
    else
     total_retlen += retlen;

    count -= retlen;
    buf += retlen;
    if (retlen == 0)
     count = 0;
   }
   else {
    kfree(kbuf);
    return ret;
   }

}
        printk("mtd_read called!!2222222222222222\n");
kfree(kbuf);
return total_retlen;
}
注意到裏面這一句:ret = mtd->read(mtd, *ppos, len, &retlen, kbuf);這個是調用MTD原始設備層的mtd.read函數了。
而我們再回頭看看m25p80.c裏probe函數里語句:
flash->mtd.erase = m25p80_erase;
flash->mtd.read = m25p80_read;
flash->mtd.write = m25p80_write;
這個就是給mtd結構體賦初值的地方。原來是這麼聯繫起來的。暈倒!
從上面的解釋可以看到這個函數
1、先申請一塊大小爲MAX_KMALLOC_SIZE的內核空間kbuf,

2、調用mtd->read將MTD設備中的數據讀入kbuf,

3、將kbuf中的數據拷貝到用戶空間buf


       可以看到,原來如此mtd是通過這些層次關係來調用底層mtd設備(m25p80.c)的數據來的。這又讓我想起了視頻video驅動裏v4l2層次了。原來複雜的內核哪裏都少不了這種層次的調用。這樣的話m25p80.c沒有open和ops結構體也正常了,因爲在mtdchar.c裏都已經做好了啊。同樣在mtdchar.c裏mtd_write()函數也看到了
           ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf);
這樣的語句就是調用m25p80.c的m25p80_write函數的語句。現在情況就一目瞭然了。



小結:原來我們要對flash讀取的時候就是要給它完成底層MTD原始設備(本例中的m25p40芯片)的加入(包括配置內核kconfig、makefile及davinci_spi_platform.c 改寫),然後這個底層設備就會通過probe函數註冊自己的mtd結構體,(struct mtd_file_info *mfi = file->private_data;struct mtd_info *mtd = mfi->mtd;)。然後中間的mtd設備層才能夠調用這個底層設備的數據,諸如:mtdchar.c裏的read、write調用。最終完成擦寫具體flash的目的。上層的應用程序要繼續研究。而設備節點代表了具體的一個mtd設備我們加載了m25p80.c以後在dev目錄下就出現了mtd4這個設備。(至於爲什麼是mtd4就需要繼續學習了)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章