基于MTD的NAND驱动开发(三)
2019-07-13 03:22发布
生成海报
五、NAND
驱动中的probe
函数
对于很多嵌入式Linux
的外设driver
来说,probe
函数将是我们遇到的第一个与具体硬件打交道,同时也相对复杂的函数。而且根据我的经验,对于很多外设的driver
来说,只要能成功实现probe
函数,那基本上完成这个外设的driver
也就成功了一多半,基于MTD
的NAND driver
就是一个典型的例子。稍后就可以看到,在NAND driver
的probe
函数中,就已经涉及到了对NAND
芯片的读写。
在基于MTD
的NAND driver
的probe
函数中,主要可以分为两部分内容,其一是与很多外设driver
类似的一些工作,如申请地址,中断,DMA
等资源,kzalloc
及初始化一些结构体,分配DMA
用的内存等等;其二就是与MTD
相关的一
些特定的工作,在这里我们将只描述第二部分内容。
1
、probe
函数中与MTD
相关的结构体
在probe
函数中,我们需要为三个与MTD
相关的结构体分配内存以及初始化,它们是struct mtd_info
、struct mtd_partition
和struct nand_chip
。其中前两者已经在前面做过说明,在此略过,这里只对struct nand_chip
做一些介绍。
struct nand_chip
是一个与NAND
芯片密切相关的结构体,主要包含三方面内容:
A.
指向一些操作NAND
芯片的函数的指针,稍后将对这些函数指针作一些说明;
B.
表示NAND
芯片特性的成员变量,主要有:
unsigned int options
:与具体的NAND
芯片相关的一些选项,如NAND_NO_AUTOINCR
,NAND_BUSWIDTH_16
等,至于这些选项具体表示什么含义,可以参考
,那里有较为详细的说明;
int page_shift
:用位表示的NAND
芯片的page
大小,如某片NAND
芯片的一个page
有512
个字节,那么page_shift
就是9
;
int phys_erase_shift
:用位表示的NAND
芯片的每次可擦除的大小,如某片NAND
芯片每次可擦除16K
字节(
通常就是一个block
的大小)
,那么phys_erase_shift
就是14
;
int bbt_erase_shift
:用位表示的bad block table
的大小,通常一个bbt
占用一个block
,所以bbt_erase_shift
通常与phys_erase_shift
相等;
int chip_shift
:用位表示的NAND
芯片的容量;
int numchips
:表示系统中有多少片NAND
芯片;
unsigned long chipsize
:NAND
芯片的大小;
int pagemask
:计算page number
时的掩码,总是等于chipsize/page
大小
- 1
;
int pagebuf
:用来保存当前读取的NAND
芯片的page number
,这样一来,下次读取的数据若还是属于同一个page
,就不必再从NAND
芯片读取了,而是从data_buf
中直接得到;
int badblockpos
:表示坏块信息保存在oob
中的第几个字节。在每个block
的第一个page
的oob
中,通常用1
或2
个字节来表示这是否为一个坏块。对于绝大多数的NAND
芯片,若page size > 512
,那么坏块信息从Byte 0
开始存储,否则就存储在Byte 5
,即第六个字节。
C.
与ecc
,oob
和bbt (bad block table)
相关的一些结构体,对于坏块及坏块管理,将在稍后做专门介绍。
2
、对NAND
芯片进行实际操作的函数
前面已经说过,MTD
为我们提供了许多default
的操作NAND
的函数,这些函数与具体的硬件(
即NAND controller)
相关,而现有的NAND controller
都有各自的特性和配置方式,MTD
当然不可能为所有的NAND controller
都提供一套这样的函数,所以在MTD
中定义的这些函数只适用于通用的NAND controller(
使用PIO
模式)
。
如果你的NAND controller
在操作或者说读写NAND
时有自己独特的方式,那就必须自己定义适用于你的NAND controller
的函数。一般来说,这些与硬件相关的函数都在struct nand_chip
结构体中定义,或者应该说是给此结构体中的函数指针赋值。为了更好的理解,我想有必要对struct nand_chip
中几个重要的函数指针做一些说明。
struct
nand_chip {
void
__iomem *
IO_ADDR_R;
void
__iomem *
IO_ADDR_W;
uint8_t
(
*
read_byte)
(
struct
mtd_info *
mtd)
;
u16 (
*
read_word)
(
struct
mtd_info *
mtd)
;
void
(
*
write_buf)
(
struct
mtd_info *
mtd,
const
uint8_t
*
buf,
int
len)
;
void
(
*
read_buf)
(
struct
mtd_info *
mtd,
uint8_t
*
buf,
int
len)
;
int
(
*
verify_buf)
(
struct
mtd_info *
mtd,
const
uint8_t
*
buf,
int
len)
;
void
(
*
select_chip)
(
struct
mtd_info *
mtd,
int
chip)
;
int
(
*
block_bad)
(
struct
mtd_info *
mtd,
loff_t ofs,
int
getchip)
;
int
(
*
block_markbad)
(
struct
mtd_info *
mtd,
loff_t ofs)
;
void
(
*
cmd_ctrl)
(
struct
mtd_info *
mtd,
int
dat,
unsigned
int
ctrl)
;
int
(
*
dev_ready)
(
struct
mtd_info *
mtd)
;
void
(
*
cmdfunc)
(
struct
mtd_info *
mtd,
unsigned
command,
int
column,
int
page_addr)
;
int
(
*
waitfunc)
(
struct
mtd_info *
mtd,
struct
nand_chip *
this
)
;
void
(
*
erase_cmd)
(
struct
mtd_info *
mtd,
int
page)
;
int
(
*
scan_bbt)
(
struct
mtd_info *
mtd)
;
int
(
*
errstat)
(
struct
mtd_info *
mtd,
struct
nand_chip *
this
,
int
state,
int
status,
int
page)
;
int
(
*
write_page)
(
struct
mtd_info *
mtd,
struct
nand_chip *
chip,
const
uint8_t
*
buf,
int
page,
int
cached,
int
raw)
;
……
struct
nand_ecc_ctrl ecc;
……
}
IO_ADDR_R
和IO_ADDR_W
:8
位NAND
芯片的读写地址,如果你的NAND controller
是用PIO
模式与NAND
芯片交互,那么只要把这两个值赋上合适的地址,就完全可以使用MTD
提供的default
的读写函数来操作NAND
芯片了。所以这两个变量视具体的NAND controller
而定,不一定用得着;
read_byte
和read_word
:从NAND
芯片读一个字节或一个字,通常MTD
会在读取NAND
芯片的ID
,STATUS
和OOB
中的坏块信息时调用这两个函数,具体是这样的流程,首先MTD
调用cmdfunc
函数,发起相应的命令,NAND
芯片收到命令后就会做好准备,最后MTD
就会调用read_byte
或read_word
函数从NAND
芯片中读取芯片的ID
,STATUS
或者OOB
;
read_buf
、write_buf
和verify_buf
:分别是从NAND
芯片读取数据到buffer
,把buffer
中的数据写入到NAND
芯片,和从NAND
芯片中读取数据并验证。调用read_buf
时的流程与read_byte
和read_word
类似,MTD
也是先调用cmdfunc
函数发起读命令(
如NAND_CMD_READ0
命令)
,接着NAND
芯片收到命令后做好准备,最后MTD
再调用read_buf
函数把NAND
芯片中的数据读取到buffer
中。调用write_buf
函数的流程与read_buf
相似;
select_chip
:因为系统中可能有不止一片NAND
芯片,所以在对NAND
芯片进行操作前,需要这个函数来指定一片NAND
芯片;
cmdfunc
:向NAND
芯片发起命令;
waitfunc
:NAND
芯片在接收到命令后,并不一定能立即响应NAND controller
的下一步动作,对有些命令,比如erase
,program
等命令,NAND
芯片需要一定的时间来完成,所以就需要这个waitfunc
来等待NAND
芯片完成命令,并再次进入准备好状态;
write_page
:把一个page
的数据写入NAND
芯片,这个函数一般不需我们实现,因为它会调用struct nand_ecc_ctrl
中的write_page_raw
或者write_page
函数,关于这两个函数将在稍后介绍。
以上提到的这些函数指针,都是REPLACEABLE
的,也就是说都是可以被替换的,根据你的NAND controller
,如果你需要自己实现相应的函数,那么只需要把你的函数赋值给这些函数指针就可以了,如果你没有赋值,那么MTD
会把它自己定义的default
的函数赋值给它们。
顺便提一下,以上所说的读写NAND
芯片的流程并不是唯一的,如果你的NAND controller
在读写NAND
芯片时有自己独特的方式,那么完全可以按照自己的方式来做。就比如我们公司芯片的NAND controller
,因为它使用DMA
的方式从NAND
芯片中读写数据,所以在我的NAND driver
中,读数据的流程是这样的:首先在cmdfunc
函数中初始化DMA
专用的buffer
,配置NAND
地址,发起命令等,在cmdfunc
中我几乎做了所有需要与NAND
芯片交互的事情,总之等cmdfunc
函数返回后,NAND
芯片中的数据就已经在DMA
专用的buffer
中了,之后MTD
会再调用read_buf
函数,所以我的read_buf
函数其实只是把数据从DMA
专用的buffer
中,拷贝到MTD
提供的buffer
中罢了。
最后,struct nand_chip
结构体中还包含了一个很重要的结构体,即struct struct nand_ecc_ctrl
,该结构体中也定义了几个很重要的函数指针。它的定义如下:
struct
nand_ecc_ctrl {
……
void
(
*
hwctl)
(
struct
mtd_info *
mtd,
int
mode)
;
int
(
*
calculate)
(
struct
mtd_info *
mtd,
const
uint8_t
*
dat,
uint8_t
*
ecc_code)
;
int
(
*
correct)
(
struct
mtd_info *
mtd,
uint8_t
*
dat,
uint8_t
*
read_ecc,
uint8_t
*
calc_ecc)
;
int
(
*
read_page_raw)
(
struct
mtd_info *
mtd,
struct
nand_chip *
chip,
uint8_t
*
buf)
;
void
(
*
write_page_raw)
(
struct
mtd_info *
mtd,
struct
nand_chip *
chip,
const
uint8_t
*
buf)
;
int
(
*
read_page)
(
struct
mtd_info *
mtd,
struct
nand_chip *
chip,
uint8_t
*
buf)
;
void
(
*
write_page)
(
struct
mtd_info *
mtd,
struct
nand_chip *
chip,
const
uint8_t
*
buf)
;
int
(
*
read_oob)
(
struct
mtd_info *
mtd,
struct
nand_chip *
chip,
int
page,
int
sndcmd)
;
int
(
*
write_oob)
(
struct
mtd_info *
mtd,
struct
nand_chip *
chip,
int
page)
;
}
;
hwctl
:这个函数用来控制硬件产生ecc
,其实它主要的工作就是控制NAND controller
向NAND
芯片发出NAND_ECC_READ
、NAND_ECC_WRITE
和NAND_ECC_READSYN
等命令,与struct nand_chip
结构体中的cmdfunc
类似,只不过发起的命令是ECC
相关的罢了;
calculate
:根据data
计算ecc
值;
correct
:根据ecc
值,判断读写数据时是否有错误发生,若有错,则立即试着纠正,纠正失败则返回错误;
read_page_raw
和write_page_raw
:从NAND
芯片中读取一个page
的原始数据和向NAND
芯片写入一个page
的原始数据,所谓的原始数据,即不对读写的数据做ecc
处理,该读写什么值就读写什么值。另外,这两个函数会读写整个page
中的所有内容,即不但会读写一个page
中MAIN
部分,还会读写OOB
部分。
read_page
和write_page
:与read_page_raw
和write_page_raw
类似,但不同的是,read_page
和write_page
在读写过程中会加入ecc
的计算,校验,和纠正等处理。
read_oob
和write_oob
:读写oob
中的内容,不包括MAIN
部分。
其实,以上提到的这几个read_xxx
和write_xxx
函数,最终都会调用struct nand_chip
中的read_buf
和write_buf
这两个函数,所以如果没有特殊需求的话,我认为不必自己实现,使用MTD
提供的default
的函数即可。
为进一步理解各函数之间的调用关系,这里提供一张从网上找到的write NAND
芯片的流程图,仅供参考:
3
、probe
函数的工作流程
由前面的说明可知,我们在要对NAND
芯片进行实际操作前已经为struct mtd_info
、struct mtd_partition
和struct nand_chip
这三个结构体分配好了内存,接下来就要为它们做一些初始化工作。
其中,我们需要为struct mtd_info
所做的初始化工作并不多,因为MTD Core
会在稍后为它做很多初始化工作,但是有一点必须由我们来做,那就是把指向struct nand_chip
结构体的指针赋给struct mtd_info
的priv
成员变量,因为MTD Core
中很多函数之间的调用都只传递struct mtd_info
,它需要通过priv
成员变量得到struct nand_chip
。
对于struct mtd_partition
的赋值,前面已经做过介绍,这里不再赘述。
所以,为struct nand_chip
的初始化,才是我们在probe
函数中的主要工作。其实这里所谓的初始化,主要就是为struct nand_chip
结构体中的众多函数指针赋值。
前面已经为struct nand_chip
结构体中的函数指针做过说明,想必你已经知道这些函数指针所指向的函数具体实现什么样的功能,负责做什么事情。那么如何让这些函数实现既定的功能呢?这就与具体的NAND controller
有关了,实在没办法多说。根据你的NAND controller
,也许你需要做很多工作,为struct nand_chip
中的每一个函数指针实现特定的函数,也或许你只需要为IO_ADDR_R
和IO_ADDR_W
赋上地址,其它则什么都不做,利用MTD
提供的函数即可。
现在假定你定义好了所有需要的与NAND
芯片交互的函数,并已经把它们赋给了struct nand_chip
结构体中的函数指针。当然,此时你还不能保证这些函数一定能正确工作,但是没有关系,probe
函数在接下来的工作中会调用到几乎所有的这些函数,你可以依次来验证和调试。当你的probe
函数能顺利通过后,那么这些函数也就基本没什么问题了,你的NAND
驱动也就已经完成了80
%了。
接下来,probe
函数就会开始与NAND
芯片进行交互了,它要做的事情主要包括这几个方面:读取NAND
芯片的ID
,然后查表得到这片NAND
芯片的如厂商,page size
,erase size
以及chip size
等信息,接着,根据struct nand_chip
中options
的值的不同,或者在NAND
芯片中的特定位置查找bad block table
,或者scan
整个NAND
芯片,并在内存中建立bad block table
。说起来复杂,但其实所有的这些动作,都可以在MTD
提供的一个叫做nand_scan
的函数中完成。
我虽然研读过nand_scan
函数中的代码,但不会在这里做情景分析式的详细说明,若你对这部分代码的实现感兴趣,可以参考以下两篇文章:
http://blog.csdn.net/binghuiliang/archive/2008/03/07/2156927.aspx
http://blog.csdn.net/binghuiliang/archive/2008/03/07/2156929.aspx
关于nand_scan
函数,在使用时我想有一个地方值得一提。
nand_scan
函数主要有两个两个函数组成,即nand_scan_ident
函数和nand_scan_tail
函数。其中nand_scan_ident
函数会读取NAND
芯片的ID
,而nand_scan_tail
函数则会查找或者建立bbt (bad block table)
。
在一般情况下,我们可以直接调用nand_scan
函数来完成所要做的工作,然而却并不总是如此,在有些情况下,我们必须分别调用nand_scan_ident
函数和nand_scan_tail
函数,因为在这两者之间,我们还需要做一些额外的工作。那么这里所谓的额外的工作,具体是做什么呢?
在《基于MTD
的NAND
驱动开发(
一)
》中介绍过一个叫做struct nand_ecclayout
的结构体,它用来定义ecc
在oob
中的布局。对于small page(
每页512 Byte)
和big page(
每页2048 Byte)
的两种NAND
芯片,它们的ecc
在oob
中的布局不尽相同。
如果你的driver
中对这两种芯片的ecc
布局与MTD
中定义的default
的布局一致,那么就很方便,直接调用nand_scan
函数即可。但如果不是,那你就需要为这两种不同的NAND
芯片分别定义你的ecc
布局。于是问题来了,因为我们在调用nand_scan_ident
函数之前,是不知道系统中的NAND
芯片是small page
类型的,还是big page
类型,然而在调用nand_scan_tail
函数之前,却必须确定NAND
芯片的oob
布局(
包括ecc
布局和坏块信息pattern)
,因为nand_scan_tail
函数在读取oob
以及处理ecc
时需要这个信息。
所以在这种情况下,我们就需要首先调用nand_scan_ident
函数,它会调用一个叫做nand_get_flash_type
的函数,MTD
就是在这个函数中读取NAND
芯片的ID
,然后就能查表(
即全局变量nand_flash_ids)
知道这片NAND
芯片的类型(
即writesize
的大小)
了。
接下来,你就可以在你的NAND
驱动中,根据writesize
的大小来区分ecc
的布局了。最后,我们就可以顺利地调用nand_scan_tail
函数了。
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮