u-boot的nand驅動寫過程分析
從命令說起,在u-boot輸入下列命令:
nand write 40008000 0 20000命令的意思是將記憶體0x40008000開始的部分寫入nand,從nand地址0開始寫,寫入長度是0x200000
回車之後,程式碼如何執行呢?命令的輸入,執行之前都已經分析過了,初始化過程也分析了
請參閱:
執行這條命令,將呼叫\u-boot-sunxi-sunxi\common\cmd_nand.c內的函式do_nand。
int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])nand write 40008000 0 20000在引數argv中,而且
argv[0] = "nand"
argv[1] = "write"
argv[2] = "40008000"
argv[3] = "0"
argv[4] = "20000"
argc = 5 引數的個數
分析一下do_nand函式的片段,篇幅關係,只保留寫操作部分:
nt do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
{
int i, ret = 0;
ulong addr;
loff_t off, size;
char *cmd, *s;
nand_info_t *nand;
#ifdef CONFIG_SYS_NAND_QUIET
int quiet = CONFIG_SYS_NAND_QUIET;
#else
int quiet = 0;
#endif
const char *quiet_str = getenv("quiet");
int dev = nand_curr_device; //當前NAND晶片,如果板上有多個晶片,則不能直接賦值,大部分板子都是一個NAND
int repeat = flag & CMD_FLAG_REPEAT;
/* at least two arguments please */
if (argc < 2)
goto usage;
if (quiet_str)
quiet = simple_strtoul(quiet_str, NULL, 0) != 0;
cmd = argv[1]; //cmd就指向命令“write”,
........判斷是什麼命令,多餘判斷刪除了..............
/* The following commands operate on the current device, unless
* overridden by a partition specifier. Note that if somehow the
* current device is invalid, it will have to be changed to a valid
* one before these commands can run, even if a partition specifier
* for another device is to be used.
*/
if (dev < 0 || dev >= CONFIG_SYS_MAX_NAND_DEVICE || //判斷晶片是否存在或是否定義
!nand_info[dev].name) {
puts("\nno devices available\n");
return 1;
}
nand = &nand_info[dev]; //獲取定義的nand晶片資訊
................
if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) { //nand讀寫操作
size_t rwsize;
ulong pagecount = 1;
int read;
int raw;
if (argc < 4)
goto usage;
addr = (ulong)simple_strtoul(argv[2], NULL, 16); //將argv[2] = "40008000"轉換成16進位制,0x40008000
read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */ //判斷讀寫操作型別
printf("\nNAND %s: ", read ? "read" : "write");
nand = &nand_info[dev];
s = strchr(cmd, '.'); //看看是否帶有擴充套件命令,如write.raw, write.jffs2等等,輸入是“write”,結果s = NULL;
if (s && !strcmp(s, ".raw")) {
......省略.....
} else { //執行這裡,計算地址偏移量,長度
if (arg_off_size(argc - 3, argv + 3, &dev,
&off, &size) != 0)
return 1;
rwsize = size;
}
if (!s || !strcmp(s, ".jffs2") || //實際執行這裡
!strcmp(s, ".e") || !strcmp(s, ".i")) {
if (read)
ret = nand_read_skip_bad(nand, off, &rwsize,
(u_char *)addr);
else
ret = nand_write_skip_bad(nand, off, &rwsize, //執行函式nand_write_skip_bad
(u_char *)addr, 0);
} else if (......省略.....) {
......省略.....
......省略.....
} else {
printf("Unknown nand command suffix '%s'.\n", s);
return 1;
}
printf(" %zu bytes %s: %s\n", rwsize,
read ? "read" : "written", ret ? "ERROR" : "OK");
return ret == 0 ? 0 : 1;
}
..........
return 0;
}
來看看函式nand_write_skip_bad,在檔案\u-boot-sunxi-sunxi\drivers\mtd\nand\nand_util.c內:
經過do_nand處理,可知引數就是輸入命令的內容:
offset 為 0
*length 為 0x200000
buffer 指向0x40008000
int nand_write_skip_bad(nand_info_t *nand, loff_t offset, size_t *length,
u_char *buffer, int flags)
{
int rval = 0, blocksize;
size_t left_to_write = *length;
u_char *p_buffer = buffer;
int need_skip;
#ifdef CONFIG_CMD_NAND_YAFFS
if (flags & WITH_YAFFS_OOB) {
if (flags & ~WITH_YAFFS_OOB)
return -EINVAL;
int pages;
pages = nand->erasesize / nand->writesize;
blocksize = (pages * nand->oobsize) + nand->erasesize;
if (*length % (nand->writesize + nand->oobsize)) {
printf ("Attempt to write incomplete page"
" in yaffs mode\n");
return -EINVAL;
}
} else
#endif
{
blocksize = nand->erasesize; //執行這裡,nand的重新整理都是以塊為單位的,所以blocksize就是重新整理的長度,對於cubieboard上的nand晶片,是1M+80K
}
/*
* nand_write() handles unaligned, partial page writes.
*
* We allow length to be unaligned, for convenience in
* using the $filesize variable.
*
* However, starting at an unaligned offset makes the
* semantics of bad block skipping ambiguous (really,
* you should only start a block skipping access at a
* partition boundary). So don't try to handle that.
*/
if ((offset & (nand->writesize - 1)) != 0) { //輸入的偏移量要以塊長度對齊
printf ("Attempt to write non page aligned data\n");
*length = 0;
return -EINVAL;
}
need_skip = check_skip_len(nand, offset, *length); //判斷是否需要越過壞塊,這裡需要壞塊讀取操作,nand驅動的一個功能
if (need_skip < 0) {
printf ("Attempt to write outside the flash area\n");
*length = 0;
return -EINVAL;
}
if (!need_skip && !(flags & WITH_DROP_FFS)) { //不需要,即寫的部分沒有壞塊
rval = nand_write (nand, offset, length, buffer); //直接寫
if (rval == 0)
return 0;
*length = 0;
printf ("NAND write to offset %llx failed %d\n",
offset, rval);
return rval;
}
while (left_to_write > 0) { // 剩下要寫的位元組數,開始就是命令輸入的0x200000
size_t block_offset = offset & (nand->erasesize - 1);
size_t write_size, truncated_write_size;
WATCHDOG_RESET ();
if (nand_block_isbad (nand, offset & ~(nand->erasesize - 1))) { //從開始的位置往後找壞塊,直到找到一個可寫的為止
printf ("Skip bad block 0x%08llx\n",
offset & ~(nand->erasesize - 1));
offset += nand->erasesize - block_offset;
continue;
}
if (left_to_write < (blocksize - block_offset)) //找到可寫的塊,判斷寫入的資料是不是小於一塊,對於cubieboard,是1M
write_size = left_to_write; //由於輸入的是0x200000即2M,因此需要寫兩次
else
write_size = blocksize - block_offset;
#ifdef CONFIG_CMD_NAND_YAFFS
.......
#endif
{
truncated_write_size = write_size;
#ifdef CONFIG_CMD_NAND_TRIMFFS
.......
#endif
rval = nand_write(nand, offset, &truncated_write_size, //呼叫nand_write,寫入資料
p_buffer);
offset += write_size; //偏移量往後移動
p_buffer += write_size; //資料指標往後移動
}
if (rval != 0) {
printf ("NAND write to offset %llx failed %d\n",
offset, rval);
*length -= left_to_write;
return rval;
}
left_to_write -= write_size; //剩下的位元組數,迴圈寫的條件
}
return 0;
}
無論如何寫,有沒有壞塊,最後都使用函式nand_write,接下來再看看這個函式
在檔案在檔案\u-boot-sunxi-sunxi\drivers\mtd\nand\nand_base.c內:
這個函式就是寫的準備,這已經執行到驅動程式碼的邏輯層了
static int nand_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const uint8_t *buf)
{
struct nand_chip *chip = mtd->priv;
int ret;
/* Do not allow writes past end of device */ 不能超過最大長度
if ((to + len) > mtd->size)
return -EINVAL;
if (!len)
return 0;
nand_get_device(chip, mtd, FL_WRITING); //獲取裝置,就是獲取需要寫的那個nand晶片的資料
chip->ops.len = len; //寫入的長度,按輸入命令,第一次時這個就是一個塊的長度
chip->ops.datbuf = (uint8_t *)buf; //資料所在的位置,第一次就是輸入的記憶體地址0x40008000處
chip->ops.oobbuf = NULL;
ret = nand_do_write_ops(mtd, to, &chip->ops); //執行寫操作
*retlen = chip->ops.retlen;
nand_release_device(mtd);
return ret;
}
再看看nand_do_write_ops函式,就在這個檔案nand_base.c內,nand_write函式的上面:
到了這裡,其實已經接近硬體操作了,如果要寫一個nand驅動,實現寫操作,
看看這個函式,就是知道需要實現的幾個操作了。下面對幾個關鍵的地方進行標記,說明寫驅動需要實現的功能
static int nand_do_write_ops(struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops)
{
int chipnr, realpage, page, blockmask, column;
struct nand_chip *chip = mtd->priv;
uint32_t writelen = ops->len;
uint32_t oobwritelen = ops->ooblen;
uint32_t oobmaxlen = ops->mode == MTD_OOB_AUTO ?
mtd->oobavail : mtd->oobsize;
uint8_t *oob = ops->oobbuf;
uint8_t *buf = ops->datbuf;
int ret, subpage;
ops->retlen = 0;
if (!writelen)
return 0;
column = to & (mtd->writesize - 1);
subpage = column || (writelen & (mtd->writesize - 1));
if (subpage && oob)
return -EINVAL;
chipnr = (int)(to >> chip->chip_shift);
chip->select_chip(mtd, chipnr); //晶片片選,由於各種CPU的片選方式或暫存器不同,或者板子電路不同,所以使用者必須自己實現這個函式
/* Check, if it is write protected */
if (nand_check_wp(mtd)) {
printk (KERN_NOTICE "nand_do_write_ops: Device is write protected\n");
return -EIO;
}
realpage = (int)(to >> chip->page_shift);
page = realpage & chip->pagemask;
blockmask = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1;
/* Invalidate the page cache, when we write to the cached page */
if (to <= (chip->pagebuf << chip->page_shift) &&
(chip->pagebuf << chip->page_shift) < (to + ops->len))
chip->pagebuf = -1;
/* If we're not given explicit OOB data, let it be 0xFF */
if (likely(!oob))
memset(chip->oob_poi, 0xff, mtd->oobsize);
/* Don't allow multipage oob writes with offset */
if (oob && ops->ooboffs && (ops->ooboffs + ops->ooblen > oobmaxlen))
return -EINVAL;
while (1) { 輸入的長度是塊,只能一頁一頁的寫,所以要迴圈寫
WATCHDOG_RESET();
int bytes = mtd->writesize;
int cached = writelen > bytes && page != blockmask;
uint8_t *wbuf = buf;
/* Partial page write ? */
if (unlikely(column || writelen < (mtd->writesize - 1))) {
cached = 0;
bytes = min_t(int, bytes - column, (int) writelen);
chip->pagebuf = -1;
memset(chip->buffers->databuf, 0xff, mtd->writesize);
memcpy(&chip->buffers->databuf[column], buf, bytes);
wbuf = chip->buffers->databuf;
}
if (unlikely(oob)) {
size_t len = min(oobwritelen, oobmaxlen);
oob = nand_fill_oob(chip, oob, len, ops);
oobwritelen -= len;
}
ret = chip->write_page(mtd, chip, wbuf, page, cached, //寫一頁,這個函式有通用的實現,若不適合自己的晶片,則需要自己實現頁寫功能
(ops->mode == MTD_OOB_RAW));
if (ret)
break;
writelen -= bytes;
if (!writelen)
break;
column = 0;
buf += bytes;
realpage++;
page = realpage & chip->pagemask;
/* Check, if we cross a chip boundary */
if (!page) {
chipnr++;
chip->select_chip(mtd, -1);
chip->select_chip(mtd, chipnr);
}
}
ops->retlen = ops->len - writelen;
if (unlikely(oob))
ops->oobretlen = ops->ooblen;
return ret;
}
再看看通用的頁寫函式
在函式int nand_scan_tail(struct mtd_info *mtd)內有兩句程式碼:
......
if (!chip->write_page)
chip->write_page = nand_write_page;
......
如果使用者沒初始化頁寫函式,則使用預設函式nand_write_page,這就是需要分析的函式
static int nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
const uint8_t *buf, int page, int cached, int raw)
{
int status;
chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page); //命令函式也有預設版本,對單次寫地址的晶片如2440,6410,可以使用預設函式,但是不適合A10
//A10的地址是兩個暫存器,每個32位,理論可以支援64位的地址寬度
//這裡執行nand命令NAND_CMD_SEQIN,值是0x80
if (unlikely(raw)) //觀察呼叫的地方,可以看出 raw = 2 ===> ops->mode == MTD_OOB_RAW
chip->ecc.write_page_raw(mtd, chip, buf); //ecc模組也有預設實現
else
chip->ecc.write_page(mtd, chip, buf);
/*
* Cached progamming disabled for now, Not sure if its worth the
* trouble. The speed gain is not very impressive. (2.3->2.6Mib/s)
*/
cached = 0;
if (!cached || !(chip->options & NAND_CACHEPRG)) {
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); ////這裡執行nand命令NAND_CMD_PAGEPROG,值是0x10
status = chip->waitfunc(mtd, chip); //等待寫完成,這個需要用自己實現
* See if operation failed and additional status checks are
* available
*/
if ((status & NAND_STATUS_FAIL) && (chip->errstat))
status = chip->errstat(mtd, chip, FL_WRITING, status,
page);
if (status & NAND_STATUS_FAIL)
return -EIO;
} else {
chip->cmdfunc(mtd, NAND_CMD_CACHEDPROG, -1, -1);
status = chip->waitfunc(mtd, chip);
}
#ifdef CONFIG_MTD_NAND_VERIFY_WRITE
/* Send command to read back the data */
chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
if (chip->verify_buf(mtd, buf, mtd->writesize))
return -EIO;
#endif
return 0;
}
再看看預設的chip->ecc.write_page_raw函式幹了什麼事情
在函式int nand_scan_tail(struct mtd_info *mtd)內有兩句程式碼:
......
if (!chip->ecc.write_page_raw)
chip->ecc.write_page_raw = nand_write_page_raw;
......
如果使用者沒初始化頁寫函式,則使用預設函式nand_write_page_raw,這就是需要分析的函式
這個函式將資料寫入,寫入什麼位置呢?還要看看它呼叫的函式chip->write_buf
static void nand_write_page_raw(struct mtd_info *mtd, struct nand_chip *chip,
const uint8_t *buf)
{
chip->write_buf(mtd, buf, mtd->writesize);
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
}
再看看預設的chip->write_buf函式
在函式int nand_scan_tail(struct mtd_info *mtd)內有兩句程式碼:
......
if (!chip->write_buf)
chip->write_buf = busw ? nand_write_buf16 : nand_write_buf;
......
如果使用者沒初始化頁寫函式,8位操作則使用預設函式nand_write_buf,16位操作則使用預設函式nand_write_buf16,
cubieboard使用的nand晶片是8位的,就看看nand_write_buf函式
void nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
{
int i;
struct nand_chip *chip = mtd->priv;
for (i = 0; i < len; i++)
writeb(buf[i], chip->IO_ADDR_W);
}
將資料寫入暫存器chip->IO_ADDR_W,即寫到nand快取
從這裡可以看出,上面的寫操作過程就是:
命令0x80-->寫資料-->命令0x10-->等待完成
檢視cubieboard上面nand晶片K9GBG08U0A的資料手冊,頁寫操作的過程真好相同,因此這個驅動可以使用
使用的前提就是需要實現一下幾個函式:
片選函式: chip->select_chip
命令操作函式:chip->cmdfunc
chip->waitfunc呼叫的兩個函式:
晶片就緒函式:chip->dev_ready
位元組讀取函式:chip->read_byte
到這裡,我都沒有分析資料結構,只描述了呼叫流程
觀察各個函式,貫穿整個過程的資料結構有兩個
struct mtd_info
struct nand_chip
這兩個資料結構在初始化分析時已經講過了
相關推薦
u-boot的nand驅動寫過程分析
從命令說起,在u-boot輸入下列命令: nand write 40008000 0 20000 命令的意思是將記憶體0x40008000開始的部分寫入nand,從nand地址0開始寫,寫入長度是0x200000回車之後,程式碼如何執行呢?命令的輸入,執行之前都已經分析過了
u-boot-201611 啟動過程分析——基於smdk2410
u-bootu-boot-201611 啟動過程分析——基於smdk2410
u-boot的配置過程分析
前面在分析主makefile的時候,我們知道執行make 100ask24x0_config這個命令會在include目錄下建立一個config.mk檔案。 那麼為什麼編譯就會生成這個檔案呢?我們來看下這個命令究竟做了什麼,在主Makefile中我們可以找到以下命令: 100ask24x0_
linux0.11字元裝置的讀寫過程分析
首先要知道linux系統/dev目錄下的各種裝置檔案(檔案屬性c打頭)並不佔用空間,你可以發現他們的大小為0位元組,他們的區別在於檔案的i節點的成員i_zone[0]的值不同,該值標識不同的裝置號。比如tty0檔案的裝置號為0x0400,tty1裝置號為0x0401,hd0
linux驅動註冊過程分析--driver_register(一)
kernel版本3.10.14 driver_register顧名思義,是驅動程式的註冊。但是很少是由我們寫的驅動直接呼叫的,例如framebuffer中呼叫platform_driver_register,i2c中呼叫i2c_add_driver等等函式註冊對應的驅動程式。雖然我們並沒有直接呼叫drive
第一個lucene程式,把一個資訊寫入到索引庫中、根據關鍵詞把物件從索引庫中提取出來、lucene讀寫過程分析
新建一個Java Project :LuceneTest 準備lucene的jar包,要加入的jar包至少有:1)lucene-core-3.1.0.jar (核心包) 2)lucene-analyzers-3.1.0.jar (分詞器) 3)lucene-h
u-boot、kernel和filesystem 執行過程分析
標題: Uboot -kerne-root 啟動流程 內容: ※uboot啟動流程 ※Kernel啟動流程 ※Root啟動流程 ※構建根檔案系統 /********************************* *u-boot
Linux核心啟動過程分析(十)-----RTC驅動分析
參考https://blog.csdn.net/xuao20060793/article/details/46433263這篇博文 RTC驅動分析: Class.c (drivers\rtc):subsys_initcall(rtc_init); static int __init
U-Boot啟動過程分析總結
做為嵌入式開發者,U-Boot的啟動是必須要熟悉的。下面分享U-Boot啟動流程。 U-Boot啟動核心的過程可以分為兩個階段,兩個階段的功能如下: 第一階段功能 硬體裝置初始化 載入U-Boot第二階段程式碼到RAM空間 設定好棧 跳轉到第二階段程式碼入口 第二階段功能 初始化本階
linux驅動篇之 driver_register 過程分析(一)
linux驅動註冊過程分析--driver_register(一) 個人筆記,歡迎轉載,請註明出處,共同分享 共同進步 http://blog.csdn.net/richard_liujh/article/details/45825333 kernel版本3.10.1
Linux 驅動 tty終端 呼叫過程分析
核心版本: 2.6.32.2 仍在修改中 2018.12.21 需進一步瞭解核心啟動過程中start_kernel以及module_init呼叫 module_init(serial8250_init); module_init(tty_init); console_init在start
lucene原始碼分析—倒排索引的寫過程
lucene將倒排索引的資訊寫入.tim和.tip檔案,這部分程式碼也是lucene最核心的一部分。倒排索引的寫過程從BlockTreeTermsWriter的write函式開始, BlockTreeTermsWriter::write public void wri
u-boot2013.01 使用裝置樹,裝置樹獲得bootargs過程分析
u-boot 裝置樹形式獲取bootargs 1. 程式呼叫過程do_bootm bootm_start(cmdtp, flag, argc, argv) bootm_load_os(images.os, &load_end, 1)
u-boot-2015.07 autoconf.mk生成過程分析
1、u-boot2015.7版本編譯沒有在頂層目錄中生成.config檔案,而生成了include/autoconf.mk和include/autoconf.mk.dep兩個檔案,並在每個模組編譯的時候包含著兩個檔案,作用相當於高版本u-boot中的.config檔案。 2、autoconf
Linux驅動的兩種載入方式過程分析
一、概念簡述 在Linux下可以通過兩種方式載入驅動程式:靜態載入和動態載入。 靜態載入就是把驅動程式直接編譯進核心,系統啟動後可以直接呼叫。靜態載入的缺點是除錯起來比較麻煩,每次修改一個地方都要重新編譯和下載核心,效率較低。若採用靜態載入的驅動較多,會導致核心容量很
對於ARM的啟動,系統升級,燒寫過程和檔案系統等方面的總結分析
本文所述的ARM的指的是Cortex A系列以及ARM9,ARM11,跑Linux作業系統。對於CortexM系列並不一定完全適用; 談到ARM以及啟動和燒寫等方面,首先我們要明確一下幾個關鍵詞:Uboot,Cmdline,啟動方式選擇,檔案系統格式,儲存介質,如NAND,
Linux下讀寫FLASH驅動——MTD裝置分析
最近在學習驅動讀寫flash的程式碼部分。經歷了可笑的過程:開始我知道flash用通過spi口來讀寫。所以就到了driver/spi 下面看相關程式碼。發現有個spidev.c裡面有read/write/ioctl等函式。而且還有一個davinci_spi_
mmc驅動的讀寫過程解析
mmc io的讀寫從mmc_queue_thread()的獲取queue裡面的request開始。 先列出呼叫棧,看下大概的呼叫順序, 下面的內容主要闡述這些函式如何工作。 host->ops->request() // sdhci_request()
【imx6ul】U-Boot 2016.03執行過程分析-ARM Cortex-A7
uboot組織架構正在朝著linux架構方向發展,不同版本稍有不同,一下以U-Boot 2016.03為例。分析入口:以u-boot.lds(其決定了各個段的排布方式)開始:1、u-boot.lds://設定輸出檔案大小端格式 OUTPUT_FORMAT("elf32-lit
【轉】Android 4.0 Launcher2源碼分析——啟動過程分析
handler flag 這一 第一次啟動 asynctask pla size ontouch wait Android的應用程序的入口定義在AndroidManifest.xml文件中可以找出:[html] <manifest xmlns:android="htt