Linux下讀寫FLASH驅動——MTD裝置分析
最近在學習驅動讀寫flash的程式碼部分。經歷了可笑的過程:開始我知道flash用通過spi口來讀寫。所以就到了driver/spi 下面看相關程式碼。發現有個spidev.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原始裝置層和硬體驅動層。(
參看高手說在應用裡要
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, /*4MHZ*/
.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 字元裝置都在 drivers/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 時{ 裁減本次操作大小 len 至 min(MAX_KMALLOC_SIZE, count); 申請一塊大小為 MAX_KMALLOC_SIZE 的核心空間 kbuf; 呼叫 mtd_info->read 將 MTD 裝置中的資料讀入 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;
/* FIXME: Use kiovec in 2.5 to lock down the user's buffers
and pass them directly to the MTD functions */
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);
}
/* Nand returns -EBADMSG on ecc errors, but it returns
* the data. For our userspace tools it is important
* to dump areas with ecc errors !
* For kernel internal usage it also might return -EUCLEAN
* to signal the caller that a bitflip has occured and has
* been corrected by the ECC algorithm.
* Userspace software which accesses NAND this way
* must be aware of the fact that it deals with NAND
*/
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;
} /* mtd_read */
注意到裡面這一句: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就需要繼續學習了)。