flash 订阅
Flash是由macromedia公司推出的交互式矢量图和 Web 动画的标准,由Adobe公司收购。做Flash动画的人被称之为闪客。网页设计者使用 Flash 创作出既漂亮又可改变尺寸的导航界面以及其他奇特的效果。Flash的前身是Future Wave公司的Future Splash,是世界上第一个商用的二维矢量动画软件,用于设计和编辑Flash文档。1996年11月,美国Macromedia公司收购了Future Wave,并将其改名为Flash。后又于2005年12月3日被Adobe公司收购。Flash通常也指Macromedia Flash Player(现Adobe Flash Player)。2012年8月15日,Flash退出Android平台,正式告别移动端。2015年12月1日,Adobe将动画制作软件Flash professional CC2015升级并改名为Animate CC 2015.5,从此与Flash技术划清界限。 [1]  2017年7月25日,Adobe Systems公司宣布,计划在2020年底逐步淘汰Flash播放器插件。 [2]  2020年6月18日,Adobe 公布了具体的终止日期--2020年12月31日。在该日期之后,Adobe 将不再为 Flash Player 发布任何更新或者安全补丁,并推荐用户尽快卸载该播放器。 [3-4] 展开全文
Flash是由macromedia公司推出的交互式矢量图和 Web 动画的标准,由Adobe公司收购。做Flash动画的人被称之为闪客。网页设计者使用 Flash 创作出既漂亮又可改变尺寸的导航界面以及其他奇特的效果。Flash的前身是Future Wave公司的Future Splash,是世界上第一个商用的二维矢量动画软件,用于设计和编辑Flash文档。1996年11月,美国Macromedia公司收购了Future Wave,并将其改名为Flash。后又于2005年12月3日被Adobe公司收购。Flash通常也指Macromedia Flash Player(现Adobe Flash Player)。2012年8月15日,Flash退出Android平台,正式告别移动端。2015年12月1日,Adobe将动画制作软件Flash professional CC2015升级并改名为Animate CC 2015.5,从此与Flash技术划清界限。 [1]  2017年7月25日,Adobe Systems公司宣布,计划在2020年底逐步淘汰Flash播放器插件。 [2]  2020年6月18日,Adobe 公布了具体的终止日期--2020年12月31日。在该日期之后,Adobe 将不再为 Flash Player 发布任何更新或者安全补丁,并推荐用户尽快卸载该播放器。 [3-4]
信息
重大事件
计划在2020年底逐步淘汰Flash播放器插件 [2]
类    别
动画创作软件
运营商
Adobe
别    名
闪客
中文名
固态存储器与动画编辑器
外文名
FLASH,Flash
开发商
Macromedia
最新版本
Adobe Animate CC 2019
Flash软件介绍
Flash是一种动画创作与应用程序开发于一身的创作软件,Adobe Flash Professional CC为创建数字动画、交互式Web站点、桌面应用程序以及手机应用程序开发提供了功能全面的创作和编辑环境。Flash广泛用于创建吸引人的应用程序,它们包含丰富的视频、声音、图形和动画。可以在Flash中创建原始内容或者从其它Adobe应用程序(如Photoshop或illustrator)导入它们,快速设计简单的动画,以及使用Adobe ActionScript 3.0开发高级的交互式项目。设计人员和开发人员可使用它来创建演示文稿、应用程序和其它允许用户交互的内容。Flash可以包含简单的动画、视频内容、复杂演示文稿和应用程序以及介于它们之间的任何内容。通常,使用Flash创作的各个内容单元称为应用程序,即使它们可能只是很简单的动画。您也可以通过添加图片、声音、视频和特殊效果,构建包含丰富媒体的Flash应用程序。 Flash出现的历史背景和前提条件:由于HTML(标准通用标记语言下的一个应用)的功能十分有限,无法达到人们的预期设计,以实现令人耳目一新的动态效果,在这种情况下,各种脚本语言应运而生,使得网页设计更加多样化。然而,程序设计总是不能很好地普及,因为它要求一定的编程能力,而人们更需要一种既简单直观又有功能强大的动画设计工具,而Flash的出现正好满足了这种需求。Flash Player 是一款能够播放小又快速的多媒体动画,以及交互式的动画、飞行标志和用macromedia flash做出的图像。Flash也支持高品质的mp3音频流、文字输入字段、交互式接口等等很多东西。这个最新版本可以观看所有的flash格式。Flash的前身是FutureSplash Animator,在出现时它仅仅作为当时交互制作软件Director和Authorware的一个小型插件,后来才由Macromedia公司出品成单独的软件。曾与Dreamweaver(网页制作工具软件)和Fireworks(图像处理软件)并成为“网页三剑客”。Flash随着互联网的发展,在Flash4版本之后嵌入了ActionScript函数调用功能,使互联网在交互应用上更加便捷。该公司及旗下软件于2007年被Adobe公司收购并进行后续开发。(Macromedia最后一个版本为Flash8,Adobe收购后第一个发布的版本为Flash CS) Flash矢量图(19张) Adobe Flash Professional CS6仅是Adobe Flash Platform开发平台中的一个产品。除了Flash之外,Adobe还提供了Flash Catalyst和Flash Builder。Flash Catalyst是一个设计工具,它无需编写代码即可快速创建富有表现力的界面和交互式内容。Flash Builder(以前称为Flex Builder)是适合于开发人员(而不是动画师或设计师)创建交互式内容的以代码为中心的环境。尽管开发平台不同,这3种工具最终都将生成相同的结果——Flash内容(SWF文件)。Flash SWF文件在浏览器上的Flash播放器中、浏览器外面的桌面上的AIR(Adobe Integrated Runtime,Adobe集成运行环境)中或者在移动电话上运行。Flash特别适用于创建通过Internet提供的内容,因为它的文件非常小。Flash是通过广泛使用矢量图形做到这一点的。与位图图形相比,矢量图形需要的内存和存储空间小很多,因为它们是以数学公式而不是大型数据集来表示的。位图图形之所以更大,是因为图像中的每个像素都需要一组单独的数据来表示。要在Flash中构建应用程序,可以使用Flash绘图工具创建图形,并将其它媒体元素导入Flash文档。接下来,定义如何以及何时使用各个元素来创建设想中的应用程序。Flash动画说到底就是“遮罩+补间动画+逐帧动画”与元件(主要是影片剪辑)的混合物,通过这些元素的不同组合,从而可以创建千变万化的效果。相关专业术语:
收起全文
精华内容
参与话题
问答
  • Flash基础知识

    万次阅读 2015-04-01 21:44:36
    向作者Sankye致敬 【编写驱动之前要了解的知识】 1. 硬件特性: 【Flash的硬件实现机制】 Flash全名叫做Flash Memory,属于非易失性存储设备(Non-volatile Memory Device)

    http://www.cnblogs.com/sankye/articles/1638852.html


    向作者Sankye致敬


    【编写驱动之前要了解的知识】

    1.       硬件特性:

    Flash的硬件实现机制】

    Flash全名叫做Flash Memory,属于非易失性存储设备(Non-volatile Memory Device),与此相对应的是易失性存储设备(Volatile Memory Device)。关于什么是非易失性/易失性,从名字中就可以看出,非易失性就是不容易丢失,数据存储在这类设备中,即使断电了,也不会丢失,这类设备,除了Flash,还有其他比较常见的入硬盘,ROM等,与此相对的,易失性就是断电了,数据就丢失了,比如大家常用的内存,不论是以前的SDRAMDDR SDRAM,还是现在的DDR2DDR3等,都是断电后,数据就没了。

     

    Flash的内部存储是MOSFET,里面有个悬浮门(Floating Gate),是真正存储数据的单元。

    Flash之前,紫外线可擦除(uv-erasable)EPROM,就已经采用用Floating Gate存储数据这一技术了。

    1.典型的Flash内存单元的物理结构

    数据在Flash内存单元中是以电荷(electrical charge) 形式存储的。存储电荷的多少,取决于图中的外部门(external gate)所被施加的电压,其控制了是向存储单元中冲入电荷还是使其释放电荷。而数据的表示,以所存储的电荷的电压是否超过一个特定的阈值Vth来表示。

     

    SLCMLC的实现机制】

    Nand Flash按照内部存储数据单元的电压的不同层次,也就是单个内存单元中,是存储1位数据,还是多位数据,可以分为SLCMLC

    1.       SLCSingle Level Cell:

    单个存储单元,只存储一位数据,表示成10.

    就是上面介绍的,对于数据的表示,单个存储单元中内部所存储电荷的电压,和某个特定的阈值电压Vth,相比,如果大于此Vth值,就是表示1,反之,小于Vth,就表示0.

    对于nand Flash的数据的写入1,就是控制External Gate去充电,使得存储的电荷够多,超过阈值Vth,就表示1了。而对于写入0,就是将其放电,电荷减少到小于Vth,就表示0了。

    关于为何Nand Flash不能从0变成1,我的理解是,物理上来说,是可以实现每一位的,从0变成1的,但是实际上,对于实际的物理实现,出于效率的考虑,如果对于,每一个存储单元都能单独控制,即,0变成1就是,对每一个存储单元单独去充电,所需要的硬件实现就很复杂和昂贵,同时,所进行对块擦除的操作,也就无法实现之前的,一闪而过的速度了,也就失去了Flash的众多特性了。

     

    2.       MLCMulti Level Cell

    SLC相对应,就是单个存储单元,可以存储多个位,比如2位,4位等。其实现机制,说起来比较简单,就是,通过控制内部电荷的多少,分成多个阈值,通过控制里面的电荷多少,而达到我们所需要的存储成不同的数据。比如,假设输入电压是Vin4V(实际没有这样的电压,此处只是为了举例方便),那么,可以设计出22次方=4个阈值, 1/4Vin1V2/4Vin2V3/4Vin3VVin4V,分别表示2位数据00011011,对于写入数据,就是充电,通过控制内部的电荷的多少,对应表示不同的数据。

    对于读取,则是通过对应的内部的电流(与Vth成反比),然后通过一系列解码电路完成读取,解析出所存储的数据。这些具体的物理实现,都是有足够精确的设备和技术,才能实现精确的数据写入和读出的。

    单个存储单元可以存储2位数据的,称作22次方=4 Level Cell,而不是2 Level Cell,这点,之前差点搞晕了。。。,同理,对于新出的单个存储单元可以存储4位数据的,称作 24次方=16 Level Cell

     

    【关于如何识别SLC还是MLC

    Nand Flash设计中,有个命令叫做Read ID,读取ID,意思是读取芯片的ID,就像大家的身份证一样,这里读取的ID中,是读取好几个字节,一般最少是4个,新的芯片,支持5个甚至更多,从这些字节中,可以解析出很多相关的信息,比如此Nand Flash内部是几个芯片(chip)所组成的,每个chip包含了几片(Plane),每一片中的页大小,块大小,等等。在这些信息中,其中有一个,就是识别此flashSLC还是MLC。下面这个就是最常见的Nand Flashdatasheet中所规定的,第3个字节,3rd byte,所表示的信息,其中就有SLC/MLC的识别信息:


     



     

    Description

    I/O7

    I/O6

    I/O5 I/O4

    I/O3 I/O2

    I/O1 I/O0

    Internal

    Chip Number

    1

    2

    4

    8

     

     

     

     

    0    0

    0    1

    1    0

    1    1

    Cell Type

    2 Level Cell

    4 Level Cell

    8 Level Cell

    16 Level Cell

     

     

     

    0     0

    0     1

    1     0

    1     1

     

    Number of

    Simultaneously

    Programmed Pages

    1

    2

    4

    8

     

     

    0     0

    0     1

    1     0

    1     1

     

     

    Interleave Program

    Between multiple chips

    Not Support

    Support

     

    0

    1

     

     

     

    Cache Program

    Not Support

    Support

    0

    1

     

     

     

     

    1.Nand Flash 3ID的含义

     

    Nand Flash的物理存储单元的阵列组织结构】

    Nand flash的内部组织结构,此处还是用图来解释,比较容易理解:

    2.Nand Flash物理存储单元的阵列组织结构

    上图是K9K8G08U0Adatasheet中的描述。

    简单解释就是:

    1.一个nand flash由很多个块(Block)组成,块的大小一般是128KB256KB512KB,此处是128KB

    2.每个块里面又包含了很多页(page)。每个页的大小,对于现在常见的nand flash多数是2KB,更新的nand flash4KB,这类的,页大小大于2KBnand flash,被称作big block,对应的发读写命令地址,一共5个周期(cycle),而老的nand flash,页大小是256B512B,这类的nand flash被称作small block,。地址周期只有4个。

    而块,也是Nand Flash的擦除操作的基本/最小单位。

    3.每一个页,对应还有一块区域,叫做空闲区域(spare area/冗余区域(redundant area),而Linux系统中,一般叫做OOBOut Of Band),这个区域,是最初基于Nand Flash的硬件特性:数据在读写时候相对容易错误,所以为了保证数据的正确性,必须要有对应的检测和纠错机制,此机制被叫做EDC(Error Detection Code)/ECCError Code Correction, 或者 Error Checking and Correcting),所以设计了多余的区域,用于放置数据的校验值。

    页是Nand Flash的写入操作的基本/最小的单位。

     

    Nand Flash数据存储单元的整体架构】

    简单说就是,常见的nand flash,内部只有一个chip,每个chip只有一个plane

    而有些复杂的,容量更大的nand flash,内部有多个chip,每个chip有多个plane。这类的nand flash,往往也有更加高级的功能,比如下面要介绍的Multi Plane ProgramInterleave Page Program等。

    比如,型号为K9K8G08U0A这个芯片(chip),内部有两个K9F4G08U0A,每个K9F4G08U0A包含了2Plane,每个Plane1Gb,所以K9F4G08U0A的大小是1Gb×22Gb256MB,因此,K9K8G08U0A内部有2K9F4G08U0A,即4Plane,总大小是4×256MB1GB

    而型号是K9WAG08U1Anand flash,内部包含了2K9K8G08U0A,所以,总容量是K9K8G08U0A的两倍=1GB×22GB,类似地K9NBG08U5A,内部包含了4K9K8G08U0A,总大小就是4×1GB4GB

     

    Flash名称的由来】

    Flash的擦除操作是以block块为单位的,与此相对应的是其他很多存储设备,是以bit位为最小读取/写入的单位,Flash是一次性地擦除整个块:在发送一个擦除命令后,一次性地将一个block,常见的块的大小是128KB/256KB。。,全部擦除为1,也就是里面的内容全部都是0xFF了,由于是一下子就擦除了,相对来说,擦除用的时间很短,可以用一闪而过来形容,所以,叫做Flash Memory。中文有的翻译为 (快速)闪存。

     

    Flash相对于普通设备的特殊性】

    1.       上面提到过的,Flash最小操作单位,有些特殊。

    一般设备,比如硬盘/内存,读取和写入都是以bit位为单位,读取一个bit的值,将某个值写入对应的地址的位,都是可以按位操作的。

    但是Flash由于物理特性,使得内部存储的数据,只能从1变成0,这点,可以从前面的内部实现机制了解到,只是方便统一充电,不方便单独的存储单元去放电,所以才说,只能从1变成0,也就是释放电荷。

    所以,总结一下Flash的特殊性如下:

     

     

    普通设备(硬盘/内存等)

    Flash

    读取/写入的叫法

    读取/写入

    读取/编程(Program)

    读取/写入的最小单位

    Bit/

    Page/

    擦除(Erase)操作的最小单位

    Bit/

    Block/ 

    擦除操作的含义

    将数据删除/全部写入0

    将整个块都擦除成全是1,也就是里面的数据都是0xFF 

    对于写操作

    直接写即可

    在写数据之前,要先擦除,然后再写

    2.Flash和普通设备相比所具有的特殊性

    注:

     之所以将写操作叫做编程,是因为,flash 和之前的EPROMEEPROM继承发展而来,而之前的EEPROM(Electrically Erasable Programmable Read-Only Memory),往里面写入数据,就叫做编程Program,之所以这么称呼,是因为其对数据的写入,是需要用电去擦除/写入的,就叫做编程。

     对于目前常见的页大小是2K/4KNand Flash,其块的大小有128KB/256KB/512KB等。而对于Nor Flash,常见的块大小有64K/32K等。

    ③在写数据之前,要先擦除,内部就都变成0xFF了,然后才能写入数据,也就是将对应位由1变成0


    Nand Flash引脚(Pin)的说明】

    3.Nand Flash引脚功能说明

    上图是常见的Nand Flash所拥有的引脚(Pin)所对应的功能,简单翻译如下:

    1.       I/O0 ~ I/O7:用于输入地址/数据/命令,输出数据

    2.       CLECommand Latch Enable,命令锁存使能,在输入命令之前,要先在模式寄存器中,设置CLE使能

    3.       ALEAddress Latch Enable,地址锁存使能,在输入地址之前,要先在模式寄存器中,设置ALE使能

    4.       CE#Chip Enable,芯片使能,在操作Nand Flash之前,要先选中此芯片,才能操作

    5.       RE#Read Enable,读使能,在读取数据之前,要先使CE#有效。

    6.       WE#Write Enable,写使能在写取数据之前,要先使WE#有效。

    7.       WP#Write Protect,写保护

    8.       R/B#:Ready/Busy Output,就绪/,主要用于在发送完编程/擦除命令后,检测这些操作是否完成,,表示编程/擦除操作仍在进行中,就绪表示操作完成.

    9.       VccPower,电源

    10.   VssGround,接地

    11.   N.CNon-Connection,未定义,未连接。

    [小常识]

    在数据手册中,你常会看到,对于一个引脚定义,有些字母上面带一横杠的,那是说明此引脚/信号是低电平有效,比如你上面看到的RE头上有个横线,就是说明,此RE是低电平有效,此外,为了书写方便,在字母后面加“#”,也是表示低电平有效,比如我上面写的CE#;如果字母头上啥都没有,就是默认的高电平有效,比如上面的CLE,就是高电平有效。

     

    【为何需要ALECLE

    突然想明白了,Nand Flash为何设计这么多的命令,把整个系统搞这么复杂的原因了:

    比如命令锁存使能(Command Latch Enable,CLE)  地址锁存使能(Address Latch EnableALE),那是因为,Nand Flash8I/O,而且是复用的,也就是,可以传数据,也可以传地址,也可以传命令,为了区分你当前传入的到底是啥,所以,先要用发一个CLE(或ALE)命令,告诉nand Flash的控制器一声,我下面要传的是命令(或地址),这样,里面才能根据传入的内容,进行对应的动作。否则,nand flash内部,怎么知道你传入的是数据,还是地址,还是命令啊,也就无法实现正确的操作了.

     

    Nand Flash只有8I/O引脚的好处】

    1.       减少外围引脚:相对于并口(Parellel)Nor Flash4852个引脚来说,的确是大大减小了引脚数目,这样封装后的芯片体积,就小很多。现在芯片在向体积更小,功能更强,功耗更低发展,减小芯片体积,就是很大的优势。同时,减少芯片接口,也意味着使用此芯片的相关的外围电路会更简化,避免了繁琐的硬件连线。

    2.       提高系统的可扩展性,因为没有像其他设备一样用物理大小对应的完全数目的addr引脚,在芯片内部换了芯片的大小等的改动,对于用全部的地址addr的引脚,那么就会引起这些引脚数目的增加,比如容量扩大一倍,地址空间/寻址空间扩大一倍,所以,地址线数目/addr引脚数目,就要多加一个,而对于统一用8I/O的引脚的Nand Flash,由于对外提供的都是统一的8个引脚,内部的芯片大小的变化或者其他的变化,对于外部使用者(比如编写nand flash驱动的人)来说,不需要关心,只是保证新的芯片,还是遵循同样的接口,同样的时序,同样的命令,就可以了。这样就提高了系统的扩展性。

     

    Nand flash的一些典型(typical)特性】

    1.页擦除时间是200us,有些慢的有800us

    2.块擦除时间是1.5ms.

    3.页数据读取到数据寄存器的时间一般是20us

    4.串行访问(Serial access)读取一个数据的时间是25ns,而一些旧的nand flash30ns,甚至是50ns

    5.输入输出端口是地址和数据以及命令一起multiplex复用的。

    以前老的Nand Flash,编程/擦除时间比较短,比如K9G8G08U0M,才5K次,而后来很多6.nand flash的编程/擦除的寿命,最多允许的次数,以前的nand flash多数是10K次,也就是1万次,而现在很多新的nand flash,技术提高了,比如,MicronMT29F1GxxABBNumonyx NAND04G-B2D/NAND08G-BxC,都可以达到100K,也就是10万次的编程/擦除。和之前常见的Nor Flash达到同样的使用寿命了。

    7.48引脚的TSOP1封装   52引脚的ULGA封装

     

    Nand Flash中的特殊硬件结构】

    由于nand flash相对其他常见设备来说,比较特殊,所以,特殊的设备,也有特殊的设计,所以,有些特殊的硬件特性,就有比较解释一下:

    1.       页寄存器(Page Register):由于Nand Flash读取和编程操作来说,一般最小单位是页,所以,nand flash在硬件设计时候,就考虑到这一特性,对于每一片,都有一个对应的区域,专门用于存放,将要写入到物理存储单元中去的或者刚从存储单元中读取出来的,一页的数据,这个数据缓存区,本质上就是一个buffer,但是只是名字叫法不同,datasheet里面叫做Page Register,此处翻译为 页寄存器,实际理解为页缓存,更为恰当些。而正是因为有些人不了解此内部结构,才容易产生之前遇到的某人的误解,以为内存里面的数据,通过Nand FlashFIFO,写入到Nand Flash里面去,就以为立刻实现了实际数据写入到物理存储单元中了。而实际上,只是写到了这个页缓存中,只有等你发了对应的编程第二阶段的确认命令0x10之后,实际的编程动作才开始,才开始把页缓存中的数据,一点点写到物理存储单元中去。

    所以,简单总结一下就是,对于数据的流向,实际是经过了如下步骤:

    4 Nand Flash读写时的数据流向

     

    Nand Flash中的坏块(Bad Block)

    Nand Flash中,一个块中含有1个或多个位是坏的,就成为其为坏块。

    坏块的稳定性是无法保证的,也就是说,不能保证你写入的数据是对的,或者写入对了,读出来也不一定对的。而正常的块,肯定是写入读出都是正常的。

    坏块有两种:

    1)一种是出厂的时候,也就是,你买到的新的,还没用过的Nand Flash,就可以包含了坏块。此类出厂时就有的坏块,被称作factory (masked)bad blockinitial bad/invalid block,在出厂之前,就会做对应的标记,标为坏块。

    具体标记的地方是,对于现在常见的页大小为2KNand Flash,是块中第一个页的oob起始位置(关于什么是页和oob,下面会有详细解释)的第1个字节(旧的小页面,pagesize512B甚至256Bnand flash,坏块标记是第6个字节),如果不是0xFF,就说明是坏块。相对应的是,所有正常的块,好的块,里面所有数据都是0xFF的。

    2)第二类叫做在使用过程中产生的,由于使用过程时间长了,在擦块除的时候,出错了,说明此块坏了,也要在程序运行过程中,发现,并且标记成坏块的。具体标记的位置,和上面一样。这类块叫做worn-out bad block

     

    对于坏块的管理,在Linux系统中,叫做坏块管理(BBMBad Block Managment),对应的会有一个表去记录好块,坏块的信息,以及坏块是出厂就有的,还是后来使用产生的,这个表叫做坏块表(BBTBad Block Table)。在Linux 内核MTD架构下的Nand Flash驱动,和UbootNand Flash驱动中,在加载完驱动之后,如果你没有加入参数主动要求跳过坏块扫描的话,那么都会去主动扫描坏块,建立必要的BBT的,以备后面坏块管理所使用。

     

    而关于好块和坏块,Nand Flash在出厂的时候,会做出保证:

    1.关于好的,可以使用的块的数目达到一定的数目,比如三星的K9G8G08U0M,整个flash一共有4096个块,出厂的时候,保证好的块至少大于3996个,也就是意思是,你新买到这个型号的nand flash,最坏的可能, 30963996100个坏块。不过,事实上,现在出厂时的坏块,比较少,绝大多数,都是使用时间长了,在使用过程中出现的。

    2.保证第一个块是好的,并且一般相对来说比较耐用。做此保证的主要原因是,很多Nand Flash坏块管理方法中,就是将第一个块,用来存储上面提到的BBT,否则,都是出错几率一样的块,那么也就不太好管理了,连放BBT的地方,都不好找了,^_^

     

    一般来说,不同型号的Nand Flash的数据手册中,也会提到,自己的这个nand flash,最多允许多少个坏块。就比如上面提到的,三星的K9G8G08U0M,最多有100个坏块。

     

    对于坏块的标记,本质上,也只是对应的flash上的某些字节的数据是非0xFF而已,所以,只要是数据,就是可以读取和写入的。也就意味着,可以写入其他值,也就把这个坏块标记信息破坏了。对于出厂时的坏块,一般是不建议将标记好的信息擦除掉的。

    uboot中有个命令是“nand scrub”就可以将块中所有的内容都擦除了,包括坏块标记,不论是出厂时的,还是后来使用过程中出现而新标记的。一般来说,不建议用这个。不过,我倒是经常用,其实也没啥大碍,呵呵。

    最好用“nand erase”只擦除好的块,对于已经标记坏块的块,不擦除。

     

     

    nand Flash中页的访问顺序】

    在一个块内,对每一个页进行编程的话,必须是顺序的,而不能是随机的。比如,一个块中有128个页,那么你只能先对page0编程,再对page1编程,。。。。,而不能随机的,比如先对page3,再page1page2.page0page4.。。。

     

    【片选无关(CE don’t-care)技术

    很多Nand flash支持一个叫做CE don’t-care的技术,字面意思就是,不关心是否片选,

    那有人会问了,如果不片选,那还能对其操作吗?答案就是,这个技术,主要用在当时是不需要选中芯片却还可以继续操作的这些情况:在某些应用,比如录音,音频播放等应用,中,外部使用的微秒(us)级的时钟周期,此处假设是比较少的2us,在进行读取一页或者对页编程时,是对Nand Flash操作,这样的串行(Serial Access)访问的周期都是20/30/50ns,都是纳秒(ns)级的,此处假设是50ns,当你已经发了对应的读或写的命令之后,接下来只是需要Nand Flash内部去自己操作,将数据读取除了或写入进去到内部的数据寄存器中而已,此处,如果可以把片选取消,CE#是低电平有效,取消片选就是拉高电平,这样会在下一个外部命令发送过来之前,即微秒量级的时间里面,即2us50ns2us,这段时间的取消片选,可以降低很少的系统功耗,但是多次的操作,就可以在很大程度上降低整体的功耗了。

    总结起来简单解释就是:由于某些外部应用的频率比较低,而Nand Flash内部操作速度比较快,所以具体读写操作的大部分时间里面,都是在等待外部命令的输入,同时却选中芯片,产生了多余的功耗,此“不关心片选”技术,就是在Nand Flash的内部的相对快速的操作(读或写)完成之后,就取消片选,以节省系统功耗。待下次外部命令/数据/地址输入来的时候,再选中芯片,即可正常继续操作了。这样,整体上,就可以大大降低系统功耗了。

    :Nand Flash的片选与否,功耗差别会有很大。如果数据没有记错的话,我之前遇到我们系统里面的nand flash的片选,大概有5mA的电流输出呢,要知道,整个系统优化之后的待机功耗,也才10mA左右的。

     

    【带EDC的拷回操作以及Sector的定义(Copy-Back Operation with EDC & Sector Definition for EDC)】

    Copy-Back功能,简单的说就是,将一个页的数据,拷贝到另一个页。

    如果没有Copy-Back功能,那么正常的做法就是,先要将那个页的数据拷贝出来放到内存的数据buffer中,读出来之后,再用写命令将这页的数据,写到新的页里面。

    Copy-Back功能的好处在于,不需要用到外部的存储空间,不需要读出来放到外部的buffer里面,而是可以直接读取数据到内部的页寄存器(page register)然后写到新的页里面去。而且,为了保证数据的正确,要硬件支持EDCError Detection Code)的,否则,在数据的拷贝过程中,可能会出现错误,并且拷贝次数多了,可能会累积更多错误。

    而对于错误检测来说,硬件一般支持的是512字节数据,对应有16字节用来存放校验产生的ECC数值,而这512字节一般叫做一个扇区。对于2K64字节大小的页来说,按照512字节分,分别叫做ABCD区,而后面的64字节的oob区域,按照16字节一个区,分别叫做EFGH区,对应存放ABCD数据区的ECC的值。

    Copy-Back编程的主要作用在于,去掉了数据串行读取出来,再串行写入进去的时间,所以,而这部分操作,是比较耗时的,所以此技术可以提高编程效率,提高系统整体性能。

     

    【多片同时编程(Simultaneously Program Multi Plane)

    对于有些新出的Nand Flash,支持同时对多个片进行编程,比如上面提到的三星的K9K8G08U0A,内部包含4(Plane),分别叫做Plane0Plane1Plane2Plane3.由于硬件上,对于每一个Plane,都有对应的大小是2048+64=2112字节的页寄存器(Page Register),使得同时支持多个Plane编程成为可能。 K9K8G08U0A支持同时对2Plane进行编程。不过要注意的是,只能对Plane0Plane1或者Plane2Plane3,同时编程,而不支持Plane0Plane2同时编程。

     

    【交错页编程(Interleave Page Program)】

    多片同时编程,是针对一个chip里面的多个Plane来说的,

    而此处的交错页编程,是指对多个chip而言的。

    可以先对一个chip,假设叫chip1,里面的一页进行编程,然后此时,chip1内部就开始将数据一点点写到页里面,就出于忙的状态了,而此时可以利用这个时间,对出于就绪状态的chip2,也进行页编程,发送对应的命令后,chip2内部也就开始慢慢的写数据到存储单元里面去了,也出于忙的状态了。此时,再去检查chip1,如果编程完成了,就可以开始下一页的编程了,然后发完命令后,就让其内部慢慢的编程吧,再去检查chip2,如果也是编程完了,也就可以进行接下来的其他页的编程了。如此,交互操作chip1chip2,就可以有效地利用时间,使得整体编程效率提高近2倍,大大提高nand flash的编程/擦写速度了。

     

    【随机输出页内数据(Random Data Output In a Page)】

    在介绍此特性之前,先要说说,与Random Data Output In a Page相对应的是,普通的,正常的,sequential data output in a page

    正常情况下,我们读取数据,都是先发读命令,然后等待数据从存储单元到内部的页数据寄存器中后,我们通过不断地将RE#(Read Enale,低电平有效)置低,然后从我们开始传入的列的起始地址,一点点读出我们要的数据,直到页的末尾,当然有可能还没到页地址的末尾,就不再读了。所谓的顺序(sequential)读取也就是,根据你之前发送的列地址的起始地址开始,每读一个字节的数据出来,内部的数据指针就加1,移到下个字节的地址,然后你再读下一个字节数据,就可以读出来你要的数据了,直到读取全部的数据出来为止。

    而此处的随机(random)读取,就是在你正常的顺序读取的过程中,先发一个随机读取的开始命令0x05命令,再传入你要将内部那个数据指针定位到具体什么地址,也就是2cycle的列地址,然后再发随机读取结束命令0xE0,然后,内部那个数据地址指针,就会移动到你所制定的位置了,你接下来再读取的数据,就是从那个制定地址开始的数据了。

    nand flash数据手册里面也说了,这样的随机读取,你可以多次操作,没限制的。

    请注意,上面你所传入的地址,都是列地址,也就是页内地址,也就是说,对于页大小为2Knand flash来说,所传入的地址,应该是小于2048+642112的。

    不过,实际在nand flash的使用中,好像这种用法很少的。绝大多数,都是顺序读取数据。

     

    【页编程】

    Nand flash的写操作叫做编程Program,编程,一般情况下,是以页为单位的。

    有的Nand Flash,比如K9K8G08U0A,支持部分页编程,但是有一些限制:在同一个页内的,连续的部分页的编程,不能超过4此。一般情况下,很少使用到部分页编程,都是以页为单位进行编程操作的。

     

    一个操作,用两个命令去实现,看起来是多余,效率不高,但是实际上,有其特殊考虑,

    至少对于块擦除来说,开始的命令0x60是擦除设置命令(erase setup comman),然后传入要擦除的块地址,然后再传入擦除确认命令(erase confirm command0xD0,以开始擦除的操作。

    这种,分两步:开始设置,最后确认的命令方式,是为了避免由于外部由于无意的/未预料而产生的噪音,比如,由于某种噪音,而产生了0x60命令,此时,即使被nand flash误认为是擦除操作,但是没有之后的确认操作0xD0nand flash就不会去擦除数据,这样使得数据更安全,不会由于噪音而误操作。


    【读(read)操作过程详解】

    以最简单的read操作为例,解释如何理解时序图,以及将时序图

    中的要求,转化为代码。

     

    解释时序图之前,让我们先要搞清楚,我们要做的事情:那就是,要从nand flash某个页里面,读取我们要的数据。

    要实现此功能,会涉及到几部分的知识,至少很容易想到的就是:需要用到哪些命令,怎么发这些命令,怎么计算所需要的地址,怎么读取我们要的数据等等。

    下面,就一步步的解释,需要做什么,以及如何去做:

    1.需要使用何种命令

    首先,是要了解,对于读取数据,要用什么命令。

    下面是datasheet中的命令集合:

    5.Nand Flash K9K8G08U0A的命令集合

    很容易看出,我们要读取数据,要用到Read命令,该命令需要2个周期,第一个周期发0x00,第二个周期发0x30

     

    2.发送命令前的准备工作以及时序图各个信号的具体含义

    知道了用何命令后,再去了解如何发送这些命令。

     [小常识]

    在开始解释前,多罗嗦一下使能这个词,以便有些读者和我以前一样,在听这类虽然对于某些专业人士说是属于最基本的词汇了,但是对于初次接触,或者接触不多的人来说,听多了,容易被搞得一头雾水:使能(Enable),是指使其(某个信号)有效,使其生效的意思,“使其”“能够”怎么怎么样。。。。比如,上面图中的CLE线号,是高电平有效,如果此时将其设为高电平,我们就叫做,将CLE使能,也就是使其生效的意思。

     

    6.Nand Flash数据读取操作的时序图

    注:此图来自三星的型号K9K8G08U0Anand flash的数据手册(datasheet)

     

    我们来一起看看,我在图6中的特意标注的①边上的黄色竖线。

    黄色竖线所处的时刻,是在发送读操作的第一个周期的命令0x00之前的那一刻。

    让我们看看,在那一刻,其所穿过好几行都对应什么值,以及进一步理解,为何要那个值。

    1)黄色竖线穿过的第一行,是CLE。还记得前面介绍命令所存使能(CLE)那个引脚吧?CLE,将CLE1,就说明你将要通过I/O复用端口发送进入Nand Flash的,是命令,而不是地址或者其他类型的数据。只有这样将CLE1,使其有效,才能去通知了内部硬件逻辑,你接下来将收到的是命令,内部硬件逻辑,才会将受到的命令,放到命令寄存器中,才能实现后面正确的操作,否则,不去将CLE1使其有效,硬件会无所适从,不知道你传入的到底是数据还是命令了。

    2)而第二行,是CE#,那一刻的值是0。这个道理很简单,你既然要向Nand Flash发命令,那么先要选中它,所以,要保证CE#为低电平,使其有效,也就是片选有效。

    3)第三行是WE#,意思是写使能。因为接下来是往nand Flash里面写命令,所以,要使得WE#有效,所以设为低电平。

    4)第四行,是ALE是低电平,而ALE是高电平有效,此时意思就是使其无效。而对应地,前面介绍的,使CLE有效,因为将要数据的是命令,而不是地址。如果在其他某些场合,比如接下来的要输入地址的时候,就要使其有效,而使CLE无效了。

    5)第五行,RE#,此时是高电平,无效。可以看到,知道后面低6阶段,才变成低电平,才有效,因为那时候,要发生读取命令,去读取数据。

    6)第六行,就是我们重点要介绍的,复用的输入输出I/O端口了,此刻,还没有输入数据,接下来,在不同的阶段,会输入或输出不同的数据/地址。

    7)第七行,R/B#,高电平,表示RReady/就绪,因为到了后面的第5阶段,硬件内部,在第四阶段,接受了外界的读取命令后,把该页的数据一点点送到页寄存器中,这段时间,属于系统在忙着干活,属于忙的阶段,所以,R/B#才变成低,表示Busy忙的状态的。

    介绍了时刻①的各个信号的值,以及为何是这个值之后,相信,后面的各个时刻,对应的不同信号的各个值,大家就会自己慢慢分析了,也就容易理解具体的操作顺序和原理了。

     

    3.如何计算出,我们要传入的地址

    在介绍具体读取数据的详细流程之前,还要做一件事,那就是,先要搞懂我们要访问的地址,以及这些地址,如何分解后,一点点传入进去,使得硬件能识别才行。

    此处还是以K9K8G08U0A为例,此nand flash,一共有8192个块,每个块内有64页,每个页是2K+64 Bytes,假设,我们要访问其中的第7000个块中的第25页中的1208字节处的地址,此时,我们就要先把具体的地址算出来:

    物理地址=块大小×块号+页大小×页号+页内地址=7000×128K+64×2K+1208=0x36B204B8,接下来,我们就看看,怎么才能把这个实际的物理地址,转化为nand Flash所要求的格式。

    在解释地址组成之前,先要来看看其datasheet中关于地址周期的介绍:

    7 Nand Flash的地址周期组成

    结合图7和图5中的23阶段,我们可以看出,此nand flash地址周期共有5个,2个列(Column)周期,3个行(Row)周期。而对于对应地,我们可以看出,实际上,列地址A0~A10,就是页内地址,地址范围是从02047,而对出的A11,理论上可以表示20484095,但是实际上,我们最多也只用到了20482011,用于表示页内的oob区域,其大小是64字节。

    对应地,A12A30,称作页号,页的号码,可以定位到具体是哪一个页。而其中,A18A30,表示对应的块号,即属于哪个块。

    简单解释完了地址组成,那么就很容易分析上面例子中的地址了:

    0x36B204B8 = 0011 0110 1011 0010 0000 0100 1011 1000,分别分配到5个地址周期就是:

    1st 周期,A7A0        1011 1000 = 0x B8

    2nd周期,A11A8       0000 0100 = 0x04

    3rd周期,A19A12     0010 0000 = 0x20

    4th周期,A27A20     0110 1011 = 0x6B

    5th周期,A30A28     0000 0011 = 0x03

    注意,与图7中对应的,*L,意思是地电平,由于未用到那些位,datasheet中强制要求设为0,所以,才有上面的2nd周期中的高4位是0000.其他的A30之后的位也是类似原理,都是0

    因此,接下来要介绍的,我们要访问第7000个块中的第25页中的1208字节处的话,所要传入的地址就是分5个周期,分别传入两个列地址的:0xB80x04,然后再传3个行地址的:0x200x6B0x03,这样硬件才能识别。

     

    4.读操作过程的解释

    准备工作终于完了,下面就可以开始解释说明,对于读操作的,上面图中标出来的,1-6个阶段,具体是什么含义。

    (1)      操作准备阶段:此处是读(Read)操作,所以,先发一个图5中读命令的第一个阶段的0x00,表示,让硬件先准备一下,接下来的操作是读。

    (2)      发送两个周期的列地址。也就是页内地址,表示,我要从一个页的什么位置开始读取数据。

    (3)      接下来再传入三个行地址。对应的也就是页号。

    (4)      然后再发一个读操作的第二个周期的命令0x30。接下来,就是硬件内部自己的事情了。

    (5)      Nand Flash内部硬件逻辑,负责去按照你的要求,根据传入的地址,找到哪个块中的哪个页,然后把整个这一页的数据,都一点点搬运到页缓存中去。而在此期间,你所能做的事,也就只需要去读取状态寄存器,看看对应的位的值,也就是R/B#那一位,是1还是00的话,就表示,系统是busy,仍在忙“(着读取数据),如果是1,就说系统活干完了,忙清了,已经把整个页的数据都搬运到页缓存里去了,你可以接下来读取你要的数据了。

    对于这里。估计有人会问了,这一个页一共2048+64字节,如果我传入的页内地址,就像上面给的1208一类的值,只是想读取10282011这部分数据,而不是页开始的0地址整个页的数据,那么内部硬件却读取整个页的数据出来,岂不是很浪费吗?答案是,的确很浪费,效率看起来不高,但是实际就是这么做的,而且本身读取整个页的数据,相对时间并不长,而且读出来之后,内部数据指针会定位到你刚才所制定的1208的那个位置。

    (6)      接下来,就是你“窃取“系统忙了半天之后的劳动成果的时候了,呵呵。通过先去Nand Flash的控制器中的数据寄存器中写入你要读取多少个字节(byte)/(word),然后就可以去Nand Flash的控制器的FIFO中,一点点读取你要的数据了。

    至此,整个Nand Flash的读操作就完成了。

    对于其他操作,可以根据我上面的分析,一点点自己去看datasheet,根据里面的时序图去分析具体的操作过程,然后对照代码,会更加清楚具体是如何实现的。

     

    Flash的类型】

    Flash的类型主要分两种,nand flashnor flash

    除了网上最流行的这个解释之外:

    NANDNOR的比较

    再多说几句:

    1.nor的成本相对高,具体读写数据时候,不容易出错。总体上,比较适合应用于存储少量的代码。

    2.Nand flash相对成本低。使用中数据读写容易出错,所以一般都需要有对应的软件或者硬件的数据校验算法,统称为ECC。由于相对来说,容量大,价格便宜,因此适合用来存储大量的数据。其在嵌入式系统中的作用,相当于PC上的硬盘,用于存储大量数据。

    所以,一个常见的应用组合就是,用小容量的Nor Flash存储启动代码,比如uboot,系统启动后,初始化对应的硬件,包括SDRAM等,然后将Nand Flash上的Linux 内核读取到内存中,做好该做的事情后,就跳转到SDRAM中去执行内核了,然后内核解压(如果是压缩内核的话,否则就直接运行了)后,开始运行,在Linux内核启动最后,去Nand Flash上,挂载根文件,比如jffs2yaffs2等,挂载完成,运行初始化脚本,启动consle交互,才运行你通过console和内核交互。至此完成整个系统启动过程。

    Nor Flash就分别存放的是UbootNand Flash存放的是Linux的内核镜像和根文件系统,以及余下的空间分成一个数据区。

     

    Nor flash,有类似于dram之类的地址总线,因此可以直接和CPU相连,CPU可以直接通过地址总线对nor flash进行访问,而nand flash没有这类的总线,只有IO接口,只能通过IO接口发送命令和地址,对nand flash内部数据进行访问。相比之下,nor flash就像是并行访问,nand flash就是串行访问,所以相对来说,前者的速度更快些。

    但是由于物理制程/制造方面的原因,导致nor nand在一些具体操作方面的特性不同:

     

    NOR

    NAND

    (备注)

    接口

    总线

    I/O接口

    这个两者最大的区别

    单个cell大小

     

    单个Cell成本

     

    读耗时

     

    单字节的编程时间

     

    多字节的编程时间

     

    擦除时间

     

    功耗

    低,但是需要额外的RAM

     

    是否可以执行代码

    不行但是一些新的芯片,可以在第一页之外执行一些小的loader1

    即是否允许,芯片内执行(XIP, eXecute In Place) (2)

    位反转(Bit twiddling/bit flip)

    几乎无限制

    1-4次,也称作 “部分页编程限制”

    也就是数据错误,0->11->0

    在芯片出厂时候是否允许坏块

    不允许

    允许

     

    3 Nand Flash  Nor Flash的区别

    1.       理论上是可以的,而且也是有人验证过可以的,只不过由于nand flash的物理特性,不能完全保证所读取的数据/代码是正确的,实际上,很少这么用而已。因为,如果真是要用到nand flashXIP,那么除了读出速度慢之外,还要保证有数据的校验,以保证读出来的,将要执行的代码/数据,是正确的。否则,系统很容易就跑飞了。。。

    2.       芯片内执行(XIP, eXecute In Place):

    http://hi.baidu.com/serial_story/blog/item/adb20a2a3f8ffe3c5243c1df.html

     

    Nand Flash的种类】

    具体再分,又可以分为

    1)Bare NAND chips:裸片,单独的nand 芯片

    2)SmartMediaCards =裸片+一层薄塑料,常用于数码相机和MP3播放器中。之所以称smart,是由于其软件smart,而不是硬件本身有啥smart之处。^_^

    3)DiskOnChip:裸片+glue logicglue logic=硬件ECC产生器+用于静态的nand 芯片控制的寄存器+直接访问一小片地址窗口,那块地址中包含了引导代码的stub桩,其可以从nand flash中拷贝真正的引导代码。

     

    spare area/oob

    Nand由于最初硬件设计时候考虑到,额外的错误校验等需要空间,专门对应每个页,额外设计了叫做spare area空区域,在其他地方,比如jffs2文件系统中,也叫做oobout of band)数据。

    其具体用途,总结起来有:

    1.       标记是否是坏快

    2.       存储ECC数据

    3.       存储一些和文件系统相关的数据,如jffs2就会用到这些空间存储一些特定信息,yaffs2文件系统,会在oob中,存放很多和自己文件系统相关的信息。

    2.       软件方面

    如果想要在Linux下编写Nand Flash驱动,那么就先要搞清楚Linux下,关于此部分的整个框架。弄明白,系统是如何管理你的nand flash的,以及,系统都帮你做了那些准备工作,而剩下的,驱动底层实现部分,你要去实现哪些功能,才能使得硬件正常工作起来。

     

    【内存技术设备,MTDMemory Technology Device)】

    MTD,是Linux的存储设备中的一个子系统。其设计此系统的目的是,对于内存类的设备,提供一个抽象层,一个接口,使得对于硬件驱动设计者来说,可以尽量少的去关心存储格式,比如FTLFFS2等,而只需要去提供最简单的底层硬件设备的读//擦除函数就可以了。而对于数据对于上层使用者来说是如何表示的,硬件驱动设计者可以不关心,而MTD存储设备子系统都帮你做好了。

    对于MTD字系统的好处,简单解释就是,他帮助你实现了,很多对于以前或者其他系统来说,本来也是你驱动设计者要去实现的很多功能。换句话说,有了MTD,使得你设计Nand Flash的驱动,所要做的事情,要少很多很多,因为大部分工作,都由MTD帮你做好了。

    当然,这个好处的一个“副作用”就是,使得我们不了解的人去理解整个Linux驱动架构,以及MTD,变得更加复杂。但是,总的说,觉得是利远远大于弊,否则,就不仅需要你理解,而且还是做更多的工作,实现更多的功能了。

    此外,还有一个重要的原因,那就是,前面提到的nand flash和普通硬盘等设备的特殊性:

    有限的通过出复用来实现输入输出命令和地址/数据等的IO接口,最小单位是页而不是常见的bit,写前需擦除等,导致了这类设备,不能像平常对待硬盘等操作一样去操作,只能采取一些特殊方法,这就诞生了MTD设备的统一抽象层。

    MTD,将nand flashnor flash和其他类型的flash等设备,统一抽象成MTD设备来管理,根据这些设备的特点,上层实现了常见的操作函数封装,底层具体的内部实现,就需要驱动设计者自己来实现了。具体的内部硬件设备的读//擦除函数,那就是你必须实现的了。

    HARD drives

    MTD device

    连续的扇区

    连续的可擦除块

    扇区都很小(512B,1024B)

    可擦除块比较大 (32KB,128KB)

    主要通过两个操作对其维护操作:读扇区,写扇区

    主要通过三个操作对其维护操作:从擦除块中读,写入擦除块,擦写可擦除块

    坏快被重新映射,并且被硬件隐藏起来了(至少是在如今常见的LBA硬盘设备中是如此)

    坏的可擦除块没有被隐藏,软件中要处理对应的坏块问题。

    HDD扇区没有擦写寿命超出的问题。

    可擦除块是有擦除次数限制的,大概是104-105.

    4.MTD设备和硬盘设备之间的区别

     

    多说一句,关于MTD更多的内容,感兴趣的,去附录中的MTD的主页去看。

    关于mtd设备驱动,感兴趣的可以去参考

    MTD原始设备与FLASH硬件驱动的对话

    MTD原始设备与FLASH硬件驱动的对话-

    那里,算是比较详细地介绍了整个流程,方便大家理解整个mtd框架和nand flash驱动。

     

    Nand flash驱动工作原理】

    在介绍具体如何写Nand Flash驱动之前,我们先要了解,大概的,整个系统,和Nand Flash相关的部分的驱动工作流程,这样,对于后面的驱动实现,才能更加清楚机制,才更容易实现,否则就是,即使写完了代码,也还是没搞懂系统是如何工作的了。

    让我们以最常见的,Linux内核中已经有的三星的Nand Flash驱动,来解释Nand Flash驱动具体流程和原理。

     

    此处是参考2.6.29版本的Linux源码中的\drivers\mtd\nand\s3c2410.c,以2410为例。

    1.       nand flash驱动加载后,第一步,就是去调用对应的init函数,s3c2410_nand_init,去将在nand flash驱动注册到Linux驱动框架中。

    2.       驱动本身,真正开始,是从probe函数,s3c2410_nand_probe->s3c24xx_nand_probe,

    probe过程中,去用clk_enable打开nand flash控制器的clock时钟,用request_mem_region去申请驱动所需要的一些内存等相关资源。然后,在s3c2410_nand_inithw中,去初始化硬件相关的部分,主要是关于时钟频率的计算,以及启用nand flash控制器,使得硬件初始化好了,后面才能正常工作。

    3.       需要多解释一下的,是这部分代码:

           for (setno = 0; setno < nr_sets; setno++, nmtd++) {

                  pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info);

    /* 调用init chip去挂载你的nand 驱动的底层函数到nand flash的结构体中,以及设置对应的ecc mode,挂载ecc相关的函数 */

                  s3c2410_nand_init_chip(info, nmtd, sets);

    /* scan_ident,扫描nand 设备,设置nand flash的默认函数,获得物理设备的具体型号以及对应各个特性参数,这部分算出来的一些值,对于nand flash来说,是最主要的参数,比如nand falsh的芯片的大小,块大小,页大小等。 */

                  nmtd->scan_res = nand_scan_ident(&nmtd->mtd,

                                               (sets) ? sets->nr_chips : 1);

     

                  if (nmtd->scan_res == 0) {

                         s3c2410_nand_update_chip(info, nmtd);

    /* scan tail,从名字就可以看出来,是扫描的后一阶段,此时,经过前面的scan_ident,我们已经获得对应nand flash的硬件的各个参数,然后就可以在scan tail中,根据这些参数,去设置其他一些重要参数,尤其是ecclayout,即ecc是如何在oob中摆放的,最后,再去进行一些初始化操作,主要是根据你的驱动,如果没有实现一些函数的话,那么就用系统默认的。 */

                         nand_scan_tail(&nmtd->mtd);

    /* add partion,根据你的nand flash的分区设置,去分区 */

                         s3c2410_nand_add_partition(info, nmtd, sets);

                  }

                  if (sets != NULL)

                         sets++;

           }

    4.       等所有的参数都计算好了,函数都挂载完毕,系统就可以正常工作了。

    上层访问你的nand falsh中的数据的时候,通过MTD层,一层层调用,最后调用到你所实现的那些底层访问硬件数据/缓存的函数中。

     

    Linuxnand flash驱动编写步骤简介】

    关于上面提到的,在nand_scan_tail的时候,系统会根据你的驱动,如果没有实现一些函数的话,那么就用系统默认的。如果实现了自己的函数,就用你的。

    估计很多人就会问了,那么到底我要实现哪些函数呢,而又有哪些是可以不实现,用系统默认的就可以了呢。

    此问题的,就是我们下面要介绍的,也就是,你要实现的,你的驱动最少要做哪些工作,才能使整个nand flash工作起来。

     

    1.       对于驱动框架部分

    其实,要了解,关于驱动框架部分,你所要做的事情的话,只要看看三星的整个nand flash驱动中的这个结构体,就差不多了:

    static struct platform_driver s3c2410_nand_driver = {

           .probe            = s3c2410_nand_probe,

           .remove         = s3c2410_nand_remove,

           .suspend = s3c24xx_nand_suspend,

           .resume         = s3c24xx_nand_resume,

           .driver           = {

                  .name     = "s3c2410-nand",

                  .owner    = THIS_MODULE,

           },

    };

     

    对于上面这个结构体,没多少要解释的。从名字,就能看出来:

    1probe就是系统“探测”,就是前面解释的整个过程,这个过程中的多数步骤,都是和你自己的nand flash相关的,尤其是那些硬件初始化部分,是你必须要自己实现的。

    2remove,就是和probe对应的,“反初始化”相关的动作。主要是释放系统相关资源和关闭硬件的时钟等常见操作了。

    (3)suspendresume,对于很多没用到电源管理的情况下,至少对于我们刚开始写基本的驱动的时候,可以不用关心,放个空函数即可。

     

    2.       对于nand flash底层操作实现部分

    而对于底层硬件操作的有些函数,总体上说,都可以在上面提到的s3c2410_nand_init_chip中找到:

    static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,

                                   struct s3c2410_nand_mtd *nmtd,

                                   struct s3c2410_nand_set *set)

    {

           struct nand_chip *chip = &nmtd->chip;

           void __iomem *regs = info->regs;

     

           chip->write_buf    = s3c2410_nand_write_buf;

           chip->read_buf     = s3c2410_nand_read_buf;

           chip->select_chip  = s3c2410_nand_select_chip;

           chip->chip_delay   = 50;

           chip->priv         = nmtd;

           chip->options    = 0;

           chip->controller   = &info->controller;

     

           switch (info->cpu_type) {

           case TYPE_S3C2410:

    /* nand flash控制器中,一般都有对应的数据寄存器,用于给你往里面写数据,表示将要读取或写入多少个字节(byte,u8)/(word,u32) ,所以,此处,你要给出地址,以便后面的操作所使用 */

                  chip->IO_ADDR_W = regs + S3C2410_NFDATA;

                  info->sel_reg   = regs + S3C2410_NFCONF;

                  info->sel_bit  = S3C2410_NFCONF_nFCE;

                  chip->cmd_ctrl  = s3c2410_nand_hwcontrol;

                  chip->dev_ready = s3c2410_nand_devready;

                  break;

    。。。。。。

          }

     

           chip->IO_ADDR_R = chip->IO_ADDR_W;

     

           nmtd->info       = info;

           nmtd->mtd.priv       = chip;

           nmtd->mtd.owner    = THIS_MODULE;

           nmtd->set        = set;

     

           if (hardware_ecc) {

                  chip->ecc.calculate = s3c2410_nand_calculate_ecc;

                  chip->ecc.correct   = s3c2410_nand_correct_data;

    /* 此处,多数情况下,你所用的Nand Flash的控制器,都是支持硬件ECC的,所以,此处设置硬件ECC(HW_ECC) ,也是充分利用硬件的特性,而如果此处不用硬件去做的ECC的话,那么下面也会去设置成NAND_ECC_SOFT,系统会用默认的软件去做ECC校验,相比之下,比硬件ECC的效率就低很多,而你的nand flash的读写,也会相应地要慢不少*/

                  chip->ecc.mode         = NAND_ECC_HW;

     

                  switch (info->cpu_type) {

                  case TYPE_S3C2410:

                         chip->ecc.hwctl         = s3c2410_nand_enable_hwecc;

                         chip->ecc.calculate = s3c2410_nand_calculate_ecc;

                         break;

    。。。。。

     

                  }

           } else {

                  chip->ecc.mode         = NAND_ECC_SOFT;

           }

     

           if (set->ecc_layout != NULL)

                  chip->ecc.layout = set->ecc_layout;

     

           if (set->disable_ecc)

                  chip->ecc.mode     = NAND_ECC_NONE;

    }

     

    而我们要实现的底层函数,也就是上面蓝色标出来的一些函数而已:

    1s3c2410_nand_write_buf  s3c2410_nand_read_buf:这是两个最基本的操作函数,其功能,就是往你的nand flash的控制器中的FIFO读写数据。一般情况下,是MTD上层的操作,比如要读取一页的数据,那么在发送完相关的读命令和等待时间之后,就会调用到你底层的read_buf,去nand FlashFIFO中,一点点把我们要的数据,读取出来,放到我们制定的内存的缓存中去。写操作也是类似,将我们内存中的数据,写到Nand FlashFIFO中去。具体的数据流向,参考上面的图4

    2s3c2410_nand_select_chip  实现Nand Flash的片选。

    3s3c2410_nand_hwcontrol:给底层发送命令或地址,或者设置具体操作的模式,都是通过此函数。

    4s3c2410_nand_devreadyNand Flash的一些操作,比如读一页数据,写入(编程)一页数据,擦除一个块,都是需要一定时间的,在命发送完成后,就是硬件开始忙着工作的时候了,而硬件什么时候完成这些操作,什么时候不忙了,变就绪了,就是通过这个函数去检查状态的。一般具体实现都是去读硬件的一个状态寄存器,其中某一位是否是1,对应着是出于“就绪/不忙”还是“忙”的状态。这个寄存器,也就是我们前面分析时序图中的R/B#

    5s3c2410_nand_enable_hwecc 在硬件支持的前提下,前面设置了硬件ECC的话,要实现这个函数,用于每次在读写操作前,通过设置对应的硬件寄存器的某些位,使得启用硬件ECC,这样在读写操作完成后,就可以去读取硬件校验产生出来的ECC数值了。

    6s3c2410_nand_calculate_ecc:如果是上面提到的硬件ECC的话,就不用我们用软件去实现校验算法了,而是直接去读取硬件产生的ECC数值就可以了。

    7s3c2410_nand_correct_data:当实际操作过程中,读取出来的数据所对应的硬件或软件计算出来的ECC,和从oob中读出来的ECC不一样的时候,就是说明数据有误了,就需要调用此函数去纠正错误。对于现在SLC常见的ECC算法来说,可以发现2位,纠正1位。如果错误大于1位,那么就无法纠正回来了。一般情况下,出错超过1位的,好像几率不大。至少我看到的不是很大。更复杂的情况和更加注重数据安全的情况下,一般是需要另外实现更高效和检错和纠错能力更强的ECC算法的。

     

    当然,除了这些你必须实现的函数之外,在你更加熟悉整个框架之后,你可以根据你自己的nand flash的特点,去实现其他一些原先用系统默认但是效率不高的函数,而用自己的更高效率的函数替代他们,以提升你的nand flash的整体性能和效率。

     

    【引用文章】

    1.Brief Intro of Nand Flash

    http://hi.baidu.com/serial_story/blog/item/3f1635d1dc041cd7562c84a1.html

    2. Samsung的型号为K9G8G08U0MNand Flash的数据手册

    要下载数据手册,可以去这里介绍的网站下载:

    samsung 4K pagesize SLC Nand Flash K9F8G08U0M datasheet + 推荐一个datasheet搜索的网站

    http://hi.baidu.com/serial_story/blog/item/7f25a03def1de309bba167c8.html

    3.Nand Falsh Read Operation

    http://hi.baidu.com/serial_story/blog/item/f06db3546eced11a3b29356c.html

    4. Memory Technology Device (MTD) Subsystem for Linux.

    http://www.linux-mtd.infradead.org/index.html



    看了<<Linux MTD源代码分析>>后对以MTD的分层结构以及各层的分工情况有了大致的了解,然而各层之间是如何进行对话的呢,对于这个问题,<<Linux MTD源代码分析>>上没有详细的去说明。

    小弟抽空研究了一下,打算从下到上,在从上到下,分两条主线来研究一下MTD原始设备与FLASH硬件驱动的对话(MTD原始设备与更上层的对话留待以后再研究)。

    以下是第一部分,从下到上的介绍FLASH硬件驱动与MTD原始设备是如何建立联系的。

    1、首先从入口函数开始:
    static int s3c24xx_nand_probe(struct device *dev, int is_s3c2440)
    {
        struct platform_device *pdev = to_platform_device(dev);
        struct s3c2410_platform_nand *plat = to_nand_plat(dev);
        //获取nand flash配置用结构体数据(dev.c中定义,详细见附录部分)
        struct s3c2410_nand_info *info;
        struct s3c2410_nand_mtd *nmtd;
        struct s3c2410_nand_set *sets;
        struct resource *res;
        int err = 0;
        int size;
        int nr_sets;
        int setno;

        pr_debug("s3c2410_nand_probe(%p)\n", dev);

        info = kmalloc(sizeof(*info), GFP_KERNEL);
        if (info == NULL) {
            printk(KERN_ERR PFX "no memory for flash info\n");
            err = -ENOMEM;
            goto exit_error;
        }

        memzero(info, sizeof(*info));
        dev_set_drvdata(dev, info);                  //以后有用

        spin_lock_init(&info->controller.lock);      //初始化自旋锁
        init_waitqueue_head(&info->controller.wq);   //初始化等待队列

        /* get the clock source and enable it */

        info->clk = clk_get(dev, "nand");
        if (IS_ERR(info->clk)) {
            printk(KERN_ERR PFX "failed to get clock");
            err = -ENOENT;
            goto exit_error;
        }

        clk_use(info->clk);
        clk_enable(info->clk);

        /* allocate and map the resource */

        /* currently we assume we have the one resource */
        res  = pdev->resource;                        //提取dev.c中定义的与设备相关的资源
        size = res->end - res->start + 1;

        info->area = request_mem_region(res->start, size, pdev->name);

        if (info->area == NULL) {
            printk(KERN_ERR PFX "cannot reserve register region\n");
            err = -ENOENT;
            goto exit_error;
        }

        info->device     = dev;
        info->platform   = plat;                     //保存好struct s3c2410_platform_nand结构数据
        info->regs       = ioremap(res->start, size);//映射nand flash用到的寄存器
        info->is_s3c2440 = is_s3c2440;               

        if (info->regs == NULL) {
            printk(KERN_ERR PFX "cannot reserve register region\n");
            err = -EIO;
            goto exit_error;
        }        

        printk(KERN_INFO PFX "mapped registers at %p\n", info->regs);

        /* initialise the hardware */

        err = s3c2410_nand_inithw(info, dev);
        //初始化s3c2410 nand flash控制,主要是配置S3C2410_NFCONF寄存器
        if (err != 0)
            goto exit_error;

        sets = (plat != NULL) ? plat->sets : NULL;   
        nr_sets = (plat != NULL) ? plat->nr_sets : 1;
       
        info->mtd_count = nr_sets;
        //我的板上只有一块nand flash,配置信息见plat-sets,数目为1。

        /* allocate our information */

        size = nr_sets * sizeof(*info->mtds);
        info->mtds = kmalloc(size, GFP_KERNEL);
        if (info->mtds == NULL) {
            printk(KERN_ERR PFX "failed to allocate mtd storage\n");
            err = -ENOMEM;
            goto exit_error;
        }

        memzero(info->mtds, size);

        /* initialise all possible chips */

        nmtd = info->mtds;

        for (setno = 0; setno < nr_sets; setno++, nmtd++) {
            pr_debug("initialising set %d (%p, info %p)\n",
                 setno, nmtd, info);
            
            s3c2410_nand_init_chip(info, nmtd, sets);

            nmtd->scan_res = nand_scan(&nmtd->mtd,
                           (sets) ? sets->nr_chips : 1);//为什么使用set->nr_chips(还没配置的东西)?

            if (nmtd->scan_res == 0) {
                s3c2410_nand_add_partition(info, nmtd, sets);
            }

            if (sets != NULL)
                sets++;
        }
        
        pr_debug("initialised ok\n");
        return 0;

     exit_error:
        s3c2410_nand_remove(dev);

        if (err == 0)
            err = -EINVAL;
        return err;
    }

    //初始化代表一片flash的struct nand_chip结构

    static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
                       struct s3c2410_nand_mtd *nmtd,
                       struct s3c2410_nand_set *set)
    {
        struct nand_chip *chip = &nmtd->chip;

        chip->IO_ADDR_R       = info->regs + S3C2410_NFDATA;   //读地址
        chip->IO_ADDR_W    = info->regs + S3C2410_NFDATA;      //写地址
        chip->hwcontrol    = s3c2410_nand_hwcontrol;   
        chip->dev_ready    = s3c2410_nand_devready;            //ready状态查询
        chip->write_buf    = s3c2410_nand_write_buf;           //写函数
        chip->read_buf     = s3c2410_nand_read_buf;            //读函数
        chip->select_chip  = s3c2410_nand_select_chip;         //片选函数
        chip->chip_delay   = 50;
        chip->priv       = nmtd;
        chip->options       = 0;
        chip->controller   = &info->controller;

        if (info->is_s3c2440) {
            chip->IO_ADDR_R     = info->regs + S3C2440_NFDATA;
            chip->IO_ADDR_W  = info->regs + S3C2440_NFDATA;
            chip->hwcontrol  = s3c2440_nand_hwcontrol;
        }

        nmtd->info       = info;
        nmtd->mtd.priv       = chip;            
        //nand_scan函数中会调用struct nand_chip *this = mtd->priv取出该struct nand_chip结构
        nmtd->set       = set;

        if (hardware_ecc) {
            chip->correct_data  = s3c2410_nand_correct_data;
            chip->enable_hwecc  = s3c2410_nand_enable_hwecc;
            chip->calculate_ecc = s3c2410_nand_calculate_ecc;
            chip->eccmode        = NAND_ECC_HW3_512;
            chip->autooob       = &nand_hw_eccoob;

            if (info->is_s3c2440) {
                chip->enable_hwecc  = s3c2440_nand_enable_hwecc;
                chip->calculate_ecc = s3c2440_nand_calculate_ecc;
            }
        } else {                                 
            chip->eccmode        = NAND_ECC_SOFT;         //ECC的类型
            }
    }

    /* command and control functions 
     *
     * Note, these all use tglx's method of changing the IO_ADDR_W field
     * to make the code simpler, and use the nand layer's code to issue the
     * command and address sequences via the proper IO ports.
     *
    */

    static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd)
    {
        struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
        struct nand_chip *chip = mtd->priv;

        switch (cmd) {
        case NAND_CTL_SETNCE:
        case NAND_CTL_CLRNCE:
            printk(KERN_ERR "%s: called for NCE\n", __FUNCTION__);
            break;

        case NAND_CTL_SETCLE:
            chip->IO_ADDR_W = info->regs + S3C2410_NFCMD;//写命令
            break;

        case NAND_CTL_SETALE:
            chip->IO_ADDR_W = info->regs + S3C2410_NFADDR;//写地址
            break;

            /* NAND_CTL_CLRCLE: */
            /* NAND_CTL_CLRALE: */
        default:
            chip->IO_ADDR_W = info->regs + S3C2410_NFDATA;//写数据
            break;
        }
    }

    /* s3c2410_nand_devready()
     *
     * returns 0 if the nand is busy, 1 if it is ready
    */

    static int s3c2410_nand_devready(struct mtd_info *mtd)
    {
        struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
        
        if (info->is_s3c2440)
            return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY;
        return readb(info->regs + S3C2410_NFSTAT) & S3C2410_NFSTAT_BUSY;//返回nand flash都忙标志
    }

    static void s3c2410_nand_write_buf(struct mtd_info *mtd,
                       const u_char *buf, int len)
    {
        struct nand_chip *this = mtd->priv;
        writesb(this->IO_ADDR_W, buf, len);//写操作
    }

    static void s3c2410_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
    {
        struct nand_chip *this = mtd->priv;
        readsb(this->IO_ADDR_R, buf, len);//读操作
    }

    /* select chip */
    /* 
     * 根据chip都值设置nand flash都片选信号:
     * chip = -1 -- 禁用nand flash
     * chip !=-1 -- 选择对应的nand flash
     */
    static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)
    {
        struct s3c2410_nand_info *info;
        struct s3c2410_nand_mtd *nmtd; 
        struct nand_chip *this = mtd->priv;
        void __iomem *reg;
        unsigned long cur;
        unsigned long bit;

        nmtd = this->priv;
        info = nmtd->info;

        bit = (info->is_s3c2440) ? S3C2440_NFCONT_nFCE : S3C2410_NFCONF_nFCE;
        reg = info->regs+((info->is_s3c2440) ? S3C2440_NFCONT:S3C2410_NFCONF);

        cur = readl(reg);

        if (chip == -1) {
            cur |= bit;
        } else {
            if (nmtd->set != NULL && chip > nmtd->set->nr_chips) {
                printk(KERN_ERR PFX "chip %d out of range\n", chip);
                return;
            }

            if (info->platform != NULL) {
                if (info->platform->select_chip != NULL)
                    (info->platform->select_chip)(nmtd->set, chip);
            }

            cur &= ~bit;
        }

        writel(cur, reg);
    }


    注:
        s3c2410_nand_init_chip填充struct nand_chip的一部分成员,nand_scan以通用nand flash的标准进行检测,并填充struct nand_chip的其它成员,必要时根据检测结果进行取舍。

    int nand_scan (struct mtd_info *mtd, int maxchips)
    {
        int i, nand_maf_id, nand_dev_id, busw, maf_id;
        struct nand_chip *this = mtd->priv;          //取出struct nand_chip结构

        /* Get buswidth to select the correct functions*/
        busw = this->options & NAND_BUSWIDTH_16;     //nand flash的位宽

        /* check for proper chip_delay setup, set 20us if not */
        if (!this->chip_delay)                     
            this->chip_delay = 20;

        /* check, if a user supplied command function given */
        if (this->cmdfunc == NULL)                  //填充命令函数
            this->cmdfunc = nand_command;

        /* check, if a user supplied wait function given */
        if (this->waitfunc == NULL)                  //填充等待函数
            this->waitfunc = nand_wait;

        if (!this->select_chip)                      //s3c2410_nand_init_chip中已定义
            this->select_chip = nand_select_chip;
        if (!this->write_byte)                       //使用默认的
            this->write_byte = busw ? nand_write_byte16 : nand_write_byte;
        if (!this->read_byte)                        //使用默认的
            this->read_byte = busw ? nand_read_byte16 : nand_read_byte;
        if (!this->write_word)                       //使用默认的
            this->write_word = nand_write_word;
        if (!this->read_word)                        //使用默认的
            this->read_word = nand_read_word;
        if (!this->block_bad)                        //使用默认的
            this->block_bad = nand_block_bad;
        if (!this->block_markbad)                    //使用默认的
            this->block_markbad = nand_default_block_markbad;
        if (!this->write_buf)                        //s3c2410_nand_init_chip中已定义
            this->write_buf = busw ? nand_write_buf16 : nand_write_buf;
        if (!this->read_buf)                         //s3c2410_nand_init_chip中已定义
            this->read_buf = busw ? nand_read_buf16 : nand_read_buf;
        if (!this->verify_buf)                       //使用默认的
            this->verify_buf = busw ? nand_verify_buf16 : nand_verify_buf;
        if (!this->scan_bbt)                         //使用默认的
            this->scan_bbt = nand_default_bbt;

        /* Select the device */
        this->select_chip(mtd, 0);       //片选,可惜在s3c2410 nand flash控制器中此操作为空

        /* Send the command for reading device ID */
        this->cmdfunc (mtd, NAND_CMD_READID, 0x00, -1);//发送读ID命令

        /* Read manufacturer and device IDs */
        nand_maf_id = this->read_byte(mtd);            //读取生产商ID
        nand_dev_id = this->read_byte(mtd);            //读取设备ID

        /* Print and store flash device information */
        for (i = 0; nand_flash_ids[i].name != NULL; i++) {   
    //保存着nand flash资料的nand_flash_ids表在include/linux/mtd/nand_ids.c文件中,详细见附录
                    
            if (nand_dev_id != nand_flash_ids[i].id)    //比较设备ID 
                continue;

            if (!mtd->name) mtd->name = nand_flash_ids[i].name;   //填充设备名
            this->chipsize = nand_flash_ids[i].chipsize << 20;    //填充设备大小
            
            /* New devices have all the information in additional id bytes */
            if (!nand_flash_ids[i].pagesize) {
                int extid;
                /* The 3rd id byte contains non relevant data ATM */
                extid = this->read_byte(mtd);
                /* The 4th id byte is the important one */
                extid = this->read_byte(mtd);
                /* Calc pagesize */
                mtd->oobblock = 1024 << (extid & 0x3);
                extid >>= 2;
                /* Calc oobsize */
                mtd->oobsize = (8 << (extid & 0x03)) * (mtd->oobblock / 512);
                extid >>= 2;
                /* Calc blocksize. Blocksize is multiples of 64KiB */
                mtd->erasesize = (64 * 1024)  << (extid & 0x03);
                extid >>= 2;
                /* Get buswidth information */
                busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
            
            } else {
                /* Old devices have this data hardcoded in the
                 * device id table */
                mtd->erasesize = nand_flash_ids[i].erasesize;   //填充檫除单元大小(16k)
                mtd->oobblock = nand_flash_ids[i].pagesize;     //填充页大小(512)
                mtd->oobsize = mtd->oobblock / 32;              //oob大小(512/32=16)
                busw = nand_flash_ids[i].options & NAND_BUSWIDTH_16;//获取nand flash表中定义的位宽
            }

            /* Try to identify manufacturer */            //比较生产商ID
            for (maf_id = 0; nand_manuf_ids[maf_id].id != 0x0; maf_id++) {
                if (nand_manuf_ids[maf_id].id == nand_maf_id)
                    break;
            }

            /* Check, if buswidth is correct. Hardware drivers should set
             * this correct ! */
            /用户定义的位宽与芯片实际的位宽不一致,取消nand flash的片选
            if (busw != (this->options & NAND_BUSWIDTH_16)) {    
                printk (KERN_INFO "NAND device: Manufacturer ID:"
                    " 0x%02x, Chip ID: 0x%02x (%s %s)\n", nand_maf_id, nand_dev_id, 
                    nand_manuf_ids[maf_id].name , mtd->name);
                printk (KERN_WARNING 
                    "NAND bus width %d instead %d bit\n", 
                        (this->options & NAND_BUSWIDTH_16) ? 16 : 8,
                        busw ? 16 : 8);
                this->select_chip(mtd, -1);//在s3c2410 nand flash控制器驱动中,此操作为空操作
                return 1;    
            }
            
            /* Calculate the address shift from the page size */ 
            //计算页、可檫除单元、nand flash大小的偏移值  
            this->page_shift = ffs(mtd->oobblock) - 1;
            this->bbt_erase_shift = this->phys_erase_shift = ffs(mtd->erasesize) - 1;
            this->chip_shift = ffs(this->chipsize) - 1;

            /* Set the bad block position */
            //标注此nand flash为大页还是小页?
            this->badblockpos = mtd->oobblock > 512 ? 
                NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS;

            /* Get chip options, preserve non chip based options */
            //用户没指定的选项从nand flash表中获取补上
            this->options &= ~NAND_CHIPOPTIONS_MSK;
            this->options |= nand_flash_ids[i].options & NAND_CHIPOPTIONS_MSK;
            /* Set this as a default. Board drivers can override it, if neccecary */
            this->options |= NAND_NO_AUTOINCR;
            /* Check if this is a not a samsung device. Do not clear the options
             * for chips which are not having an extended id.
             */    
            if (nand_maf_id != NAND_MFR_SAMSUNG && !nand_flash_ids[i].pagesize)
                this->options &= ~NAND_SAMSUNG_LP_OPTIONS;
            
            /* Check for AND chips with 4 page planes */
            if (this->options & NAND_4PAGE_ARRAY)
                this->erase_cmd = multi_erase_cmd;
            else
                this->erase_cmd = single_erase_cmd;      

            /* Do not replace user supplied command function ! */
            if (mtd->oobblock > 512 && this->cmdfunc == nand_command)
                this->cmdfunc = nand_command_lp;
                    
            printk (KERN_INFO "NAND device: Manufacturer ID:"
                " 0x%02x, Chip ID: 0x%02x (%s %s)\n", nand_maf_id, nand_dev_id, 
                nand_manuf_ids[maf_id].name , nand_flash_ids[i].name);
            break;
        }//好的,检测结束^_^

        if (!nand_flash_ids[i].name) {      
            printk (KERN_WARNING "No NAND device found!!!\n");
            this->select_chip(mtd, -1);
            return 1;
        }

        //统计一下同种类型的nand flash有多少块(我板上只有一块)
        for (i=1; i < maxchips; i++) {
            this->select_chip(mtd, i);

            /* Send the command for reading device ID */
            this->cmdfunc (mtd, NAND_CMD_READID, 0x00, -1);

            /* Read manufacturer and device IDs */
            if (nand_maf_id != this->read_byte(mtd) ||
                nand_dev_id != this->read_byte(mtd))
                break;
        }
        if (i > 1)
            printk(KERN_INFO "%d NAND chips detected\n", i);
        
        /* Allocate buffers, if neccecary */
        if (!this->oob_buf) {
            size_t len;
            //求出一个檫除单元64K中oob所占用的总空间
            len = mtd->oobsize << (this->phys_erase_shift - this->page_shift);
            this->oob_buf = kmalloc (len, GFP_KERNEL);
            if (!this->oob_buf) {
                printk (KERN_ERR "nand_scan(): Cannot allocate oob_buf\n");
                return -ENOMEM;
            }
            this->options |= NAND_OOBBUF_ALLOC;//oob空间已分配,置相应的标志位
        }
        
        if (!this->data_buf) {
            size_t len;
            len = mtd->oobblock + mtd->oobsize;//512+16=128
            this->data_buf = kmalloc (len, GFP_KERNEL);
            if (!this->data_buf) {
                if (this->options & NAND_OOBBUF_ALLOC)
                    kfree (this->oob_buf);
                printk (KERN_ERR "nand_scan(): Cannot allocate data_buf\n");
                return -ENOMEM;
            }
            this->options |= NAND_DATABUF_ALLOC;//数据空间已分配,置相应的标志位
        }

        /* Store the number of chips and calc total size for mtd */
        this->numchips = i;//记录nand flash片数
        mtd->size = i * this->chipsize;//计算出nand flash总大小
        /* Convert chipsize to number of pages per chip -1. */
        this->pagemask = (this->chipsize >> this->page_shift) - 1;//(64M>>9)-1=128k-1=0x1ffff

        /* Preset the internal oob buffer */
        //oob_buf全部置为0xff
        memset(this->oob_buf, 0xff, mtd->oobsize << (this->phys_erase_shift - this->page_shift));

        /* If no default placement scheme is given, select an
         * appropriate one */
        if (!this->autooob) {   //我们选用的是NAND_ECC_SOFT,autooob未设置
            /* Select the appropriate default oob placement scheme for
             * placement agnostic filesystems */
            switch (mtd->oobsize) { 
            case 8:
                this->autooob = &nand_oob_8;
                break;
            case 16:
                this->autooob = &nand_oob_16;//我们的nand flash属于这一类
                break;
            case 64:
                this->autooob = &nand_oob_64;
                break;
            default:
                printk (KERN_WARNING "No oob scheme defined for oobsize %d\n",
                    mtd->oobsize);
                BUG();
            }
        }
    注:
        ECC的东西不是很懂,先跳过^_^   


        /* The number of bytes available for the filesystem to place fs dependend
         * oob data */
        mtd->oobavail = 0;
        for (i = 0; this->autooob->oobfree[i][1]; i++)
            mtd->oobavail += this->autooob->oobfree[i][1];

        /* 
         * check ECC mode, default to software
         * if 3byte/512byte hardware ECC is selected and we have 256 byte pagesize
         * fallback to software ECC 
        */
        this->eccsize = 256;    /* set default eccsize */    
        this->eccbytes = 3;

        switch (this->eccmode) {
        case NAND_ECC_HW12_2048:
            if (mtd->oobblock < 2048) {
                printk(KERN_WARNING "2048 byte HW ECC not possible on %d byte page size, fallback to SW ECC\n",
                       mtd->oobblock);
                this->eccmode = NAND_ECC_SOFT;
                this->calculate_ecc = nand_calculate_ecc;
                this->correct_data = nand_correct_data;
            } else
                this->eccsize = 2048;
            break;

        case NAND_ECC_HW3_512: 
        case NAND_ECC_HW6_512: 
        case NAND_ECC_HW8_512: 
            if (mtd->oobblock == 256) {
                printk (KERN_WARNING "512 byte HW ECC not possible on 256 Byte pagesize, fallback to SW ECC \n");
                this->eccmode = NAND_ECC_SOFT;
                this->calculate_ecc = nand_calculate_ecc;
                this->correct_data = nand_correct_data;
            } else 
                this->eccsize = 512; /* set eccsize to 512 */
            break;
                
        case NAND_ECC_HW3_256:
            break;
            
        case NAND_ECC_NONE: 
            printk (KERN_WARNING "NAND_ECC_NONE selected by board driver. This is not recommended !!\n");
            this->eccmode = NAND_ECC_NONE;
            break;

        case NAND_ECC_SOFT:    
            this->calculate_ecc = nand_calculate_ecc;
            this->correct_data = nand_correct_data;
            break;

        default:
            printk (KERN_WARNING "Invalid NAND_ECC_MODE %d\n", this->eccmode);
            BUG();    
        }    

        /* Check hardware ecc function availability and adjust number of ecc bytes per 
         * calculation step
        */
        switch (this->eccmode) {
        case NAND_ECC_HW12_2048:
            this->eccbytes += 4;
        case NAND_ECC_HW8_512: 
            this->eccbytes += 2;
        case NAND_ECC_HW6_512: 
            this->eccbytes += 3;
        case NAND_ECC_HW3_512: 
        case NAND_ECC_HW3_256:
            if (this->calculate_ecc && this->correct_data && this->enable_hwecc)
                break;
            printk (KERN_WARNING "No ECC functions supplied, Hardware ECC not possible\n");
            BUG();    
        }
            
        mtd->eccsize = this->eccsize;
        
        /* Set the number of read / write steps for one page to ensure ECC generation */
        switch (this->eccmode) {
        case NAND_ECC_HW12_2048:
            this->eccsteps = mtd->oobblock / 2048;
            break;
        case NAND_ECC_HW3_512:
        case NAND_ECC_HW6_512:
        case NAND_ECC_HW8_512:
            this->eccsteps = mtd->oobblock / 512;
            break;
        case NAND_ECC_HW3_256:
        case NAND_ECC_SOFT:    
            this->eccsteps = mtd->oobblock / 256;
            break;
            
        case NAND_ECC_NONE: 
            this->eccsteps = 1;
            break;
        }
        
        /* Initialize state, waitqueue and spinlock */
        this->state = FL_READY;
        init_waitqueue_head (&this->wq);
        spin_lock_init (&this->chip_lock);

        /* De-select the device */
        this->select_chip(mtd, -1);

        /* Invalidate the pagebuffer reference */
        this->pagebuf = -1;

        /* Fill in remaining MTD driver data */
        //填充mtd结构的其它部分
        mtd->type = MTD_NANDFLASH;
        mtd->flags = MTD_CAP_NANDFLASH | MTD_ECC;
        mtd->ecctype = MTD_ECC_SW;
        mtd->erase = nand_erase;
        mtd->point = NULL;
        mtd->unpoint = NULL;
        mtd->read = nand_read;
        /* nand_read->nand_do_read_ecc->read_buf->s3c2410_nand_read_buf */
        mtd->write = nand_write;
        /* nand_write->nand_write_ecc->nand_write_page->write_buf->s3c2410_nand_write_buf */
        mtd->read_ecc = nand_read_ecc;
        mtd->write_ecc = nand_write_ecc;
        mtd->read_oob = nand_read_oob;
        mtd->write_oob = nand_write_oob;
        mtd->readv = NULL;
        mtd->writev = nand_writev;
        mtd->writev_ecc = nand_writev_ecc;
        mtd->sync = nand_sync;
        mtd->lock = NULL;
        mtd->unlock = NULL;
        mtd->suspend = NULL;
        mtd->resume = NULL;
        mtd->block_isbad = nand_block_isbad;
        mtd->block_markbad = nand_block_markbad;

        /* and make the autooob the default one */
        memcpy(&mtd->oobinfo, this->autooob, sizeof(mtd->oobinfo));

        mtd->owner = THIS_MODULE;
        
        /* Check, if we should skip the bad block table scan */
        if (this->options & NAND_SKIP_BBTSCAN)
            return 0;

        /* Build bad block table */
        return this->scan_bbt (mtd);
    }

    /**
     * nand_command - [DEFAULT] Send command to NAND device
     * @mtd:    MTD device structure
     * @command:    the command to be sent
     * @column:    the column address for this command, -1 if none
     * @page_addr:    the page address for this command, -1 if none
     *
     * Send command to NAND device. This function is used for small page
     * devices (256/512 Bytes per page)
     */
    static void nand_command (struct mtd_info *mtd, unsigned command, int column, int page_addr)
    {
        register struct nand_chip *this = mtd->priv;

        /* Begin command latch cycle */
        this->hwcontrol(mtd, NAND_CTL_SETCLE);    //选择写入S3C2410_NFCMD寄存器
        /*
         * Write out the command to the device.
         */
        if (command == NAND_CMD_SEQIN) {
            int readcmd;

            if (column >= mtd->oobblock) {        //读/写位置超出512,读oob_data
                /* OOB area */
                column -= mtd->oobblock;
                readcmd = NAND_CMD_READOOB;
            } else if (column < 256) {            //读/写位置在前512,使用read0命令
                /* First 256 bytes --> READ0 */
                readcmd = NAND_CMD_READ0;
            } else {                              //读/写位置在后512,使用read1命令
                column -= 256;
                readcmd = NAND_CMD_READ1;
            }
            this->write_byte(mtd, readcmd);        //写入具体命令
        }
        this->write_byte(mtd, command);

        /* Set ALE and clear CLE to start address cycle */
        /* 清楚CLE,锁存命令;置位ALE,开始传输地址 */
        this->hwcontrol(mtd, NAND_CTL_CLRCLE);      //锁存命令

        if (column != -1 || page_addr != -1) {
            this->hwcontrol(mtd, NAND_CTL_SETALE);  //选择写入S3C2410_NFADDR寄存器

            /* Serially input address */
            if (column != -1) {
                /* Adjust columns for 16 bit buswidth */
                if (this->options & NAND_BUSWIDTH_16)
                    column >>= 1;
                this->write_byte(mtd, column);      //写入列地址
            }
            if (page_addr != -1) {                  //写入页地址(分三个字节写入)
                this->write_byte(mtd, (unsigned char) (page_addr & 0xff));
                this->write_byte(mtd, (unsigned char) ((page_addr >> 8) & 0xff));
                /* One more address cycle for devices > 32MiB */
                if (this->chipsize > (32 << 20))
                    this->write_byte(mtd, (unsigned char) ((page_addr >> 16) & 0x0f));
            }
            /* Latch in address */
            /* 锁存地址 */
            this->hwcontrol(mtd, NAND_CTL_CLRALE);
        }

        /* 
         * program and erase have their own busy handlers 
         * status and sequential in needs no delay
        */
        switch (command) {
                
        case NAND_CMD_PAGEPROG:
        case NAND_CMD_ERASE1:
        case NAND_CMD_ERASE2:
        case NAND_CMD_SEQIN:
        case NAND_CMD_STATUS:
            return;

        case NAND_CMD_RESET:      //复位操作
                                  // 等待nand flash become ready
            if (this->dev_ready)  //判断nand flash 是否busy(1:ready 0:busy)
                break;
            udelay(this->chip_delay);
            this->hwcontrol(mtd, NAND_CTL_SETCLE);
            this->write_byte(mtd, NAND_CMD_STATUS);
            this->hwcontrol(mtd, NAND_CTL_CLRCLE);
            while ( !(this->read_byte(mtd) & NAND_STATUS_READY));
            return;

        /* This applies to read commands */    
        default:
            /* 
             * If we don't have access to the busy pin, we apply the given
             * command delay
            */
            if (!this->dev_ready) {
                udelay (this->chip_delay);//稍作延迟
                return;
            }    
        }
        /* Apply this short delay always to ensure that we do wait tWB in
         * any case on any machine. */
        ndelay (100);

        nand_wait_ready(mtd);
    }


    /* 
     * Wait for the ready pin, after a command
     * The timeout is catched later.
     */
    static void nand_wait_ready(struct mtd_info *mtd)
    {
        struct nand_chip *this = mtd->priv;
        unsigned long    timeo = jiffies + 2;

        /* wait until command is processed or timeout occures */
        do {
            if (this->dev_ready(mtd))          //简单调用this->dev_ready(s3c2410_nand_devready)函数                                             等待nand flash become ready
                return;
            touch_softlockup_watchdog();
        } while (time_before(jiffies, timeo));    
    }

    /**
     * nand_wait - [DEFAULT]  wait until the command is done
     * @mtd:    MTD device structure
     * @this:    NAND chip structure
     * @state:    state to select the max. timeout value
     *
     * Wait for command done. This applies to erase and program only
     * Erase can take up to 400ms and program up to 20ms according to 
     * general NAND and SmartMedia specs
     *
    */
    /* 等待知道命令传输完成,适用于檫除和写入命令 */
    static int nand_wait(struct mtd_info *mtd, struct nand_chip *this, int state)
    {

        unsigned long    timeo = jiffies;
        int    status;
        
        if (state == FL_ERASING)
             timeo += (HZ * 400) / 1000;//檫除操作的话,时间相对要长一些
        else
             timeo += (HZ * 20) / 1000;

        /* Apply this short delay always to ensure that we do wait tWB in
         * any case on any machine. */
        ndelay (100);

        if ((state == FL_ERASING) && (this->options & NAND_IS_AND))
            this->cmdfunc (mtd, NAND_CMD_STATUS_MULTI, -1, -1);
        else    
            this->cmdfunc (mtd, NAND_CMD_STATUS, -1, -1);

        while (time_before(jiffies, timeo)) {        
            /* Check, if we were interrupted */
            if (this->state != state)
                return 0;
            /* 等待nand flash become ready */
            if (this->dev_ready) {
                if (this->dev_ready(mtd))
                    break;    
            } else {
                if (this->read_byte(mtd) & NAND_STATUS_READY)
                    break;
            }
            cond_resched();
        }
        status = (int) this->read_byte(mtd);
        return status;
    }

    /**
     * nand_block_bad - [DEFAULT] Read bad block marker from the chip
     * 检查nand flash中某一页是否为坏块
     * @mtd:    MTD device structure
     * @ofs:    offset from device start
     * @getchip:    0, if the chip is already selected
     *
     * Check, if the block is bad. 
     */
    static int nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
    {
        int page, chipnr, res = 0;
        struct nand_chip *this = mtd->priv;
        u16 bad;

        if (getchip) {
            page = (int)(ofs >> this->page_shift);
            chipnr = (int)(ofs >> this->chip_shift);

            /* Grab the lock and see if the device is available */
            nand_get_device (this, mtd, FL_READING);

            /* Select the NAND device */
            this->select_chip(mtd, chipnr);
        } else 
            page = (int) ofs;    

        if (this->options & NAND_BUSWIDTH_16) {
            this->cmdfunc (mtd, NAND_CMD_READOOB, this->badblockpos & 0xFE, page & this->pagemask);
            bad = cpu_to_le16(this->read_word(mtd));
            if (this->badblockpos & 0x1)
                bad >>= 1;
            if ((bad & 0xFF) != 0xff)
                res = 1;
        } else {
            this->cmdfunc (mtd, NAND_CMD_READOOB, this->badblockpos, page & this->pagemask);
            /* 发送读oob_data命令(oob_data的badblockpos (第6)位记录着坏块标志) */
            if (this->read_byte(mtd) != 0xff)//坏块
                res = 1;
        }
            
        if (getchip) {
            /* Deselect and wake up anyone waiting on the device */
            nand_release_device(mtd);
        }    
        
        return res;
    }

    /**
     * nand_default_block_markbad - [DEFAULT] mark a block bad
     * 标志坏块
     * @mtd:    MTD device structure
     * @ofs:    offset from device start
     *
     * This is the default implementation, which can be overridden by
     * a hardware specific driver.
    */
    static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
    {
        struct nand_chip *this = mtd->priv;
        u_char buf[2] = {0, 0};
        size_t    retlen;
        int block;
        
        /* Get block number */
        block = ((int) ofs) >> this->bbt_erase_shift;
        if (this->bbt)
            this->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1);
        /* 
           这个暂时不是很好说:内核维护一个标志bad block表,使用2bit来表示1block。
           这个表在开机的时候通过扫描nand flash每个block的头两页的oob数据来生成,
           发现坏块后至相应的block标志位为非零(有时候至3,但有时候至1,还没搞明白有什么不同)
         */

        /* Do we have a flash based bad block table ? */
        if (this->options & NAND_USE_FLASH_BBT)//samsun nand flash不属于这种,暂时不去研究,以后同
            return nand_update_bbt (mtd, ofs);
            
        /* We write two bytes, so we dont have to mess with 16 bit access */
        ofs += mtd->oobsize + (this->badblockpos & ~0x01);//???????????????
        return nand_write_oob (mtd, ofs , 2, &retlen, buf);
    }

    /**
     * nand_verify_buf - [DEFAULT] Verify chip data against buffer
     * 检验nand flash与buffer的数据是否一致 
     * @mtd:    MTD device structure
     * @buf:    buffer containing the data to compare
     * @len:    number of bytes to compare
     *
     * Default verify function for 8bit buswith
     */
    static int nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
    {
        int i;
        struct nand_chip *this = mtd->priv;

        for (i=0; i<len; i++)
            if (buf[i] != readb(this->IO_ADDR_R))
                return -EFAULT;

        return 0;
    }

    /**
     * nand_default_bbt - [NAND Interface] Select a default bad block table for the device 
     * @mtd:    MTD device structure
     *
     * This function selects the default bad block table
     * support for the device and calls the nand_scan_bbt function
     *
    */
    int nand_default_bbt (struct mtd_info *mtd)
    {
        struct nand_chip *this = mtd->priv;
        
        /* Default for AG-AND. We must use a flash based 
         * bad block table as the devices have factory marked
         * _good_ blocks. Erasing those blocks leads to loss
         * of the good / bad information, so we _must_ store
         * this information in a good / bad table during 
         * startup
        */
        if (this->options & NAND_IS_AND) {
            /* Use the default pattern descriptors */
            if (!this->bbt_td) {    
                this->bbt_td = &bbt_main_descr;
                this->bbt_md = &bbt_mirror_descr;
            }    
            this->options |= NAND_USE_FLASH_BBT;
            return nand_scan_bbt (mtd, &agand_flashbased);
        }
        
        
        /* Is a flash based bad block table requested ? */
        if (this->options & NAND_USE_FLASH_BBT) {
            /* Use the default pattern descriptors */    
            if (!this->bbt_td) {    
                this->bbt_td = &bbt_main_descr;
                this->bbt_md = &bbt_mirror_descr;
            }
            if (!this->badblock_pattern) {
                this->badblock_pattern = (mtd->oobblock > 512) ?
                    &largepage_flashbased : &smallpage_flashbased;
            }
        } else {      //samsun nand flash的坏块表不存在与nand flash里面,需要扫描来生成。
            this->bbt_td = NULL;
            this->bbt_md = NULL;
            if (!this->badblock_pattern) {
                this->badblock_pattern = (mtd->oobblock > 512) ?
                    &largepage_memorybased : &smallpage_memorybased;
            }
        }
        return nand_scan_bbt (mtd, this->badblock_pattern);
    }

    /**
     * nand_scan_bbt - [NAND Interface] scan, find, read and maybe create bad block table(s)
     * @mtd:    MTD device structure
     * @bd:        descriptor for the good/bad block search pattern
     *
     * The function checks, if a bad block table(s) is/are already 
     * available. If not it scans the device for manufacturer
     * marked good / bad blocks and writes the bad block table(s) to
     * the selected place.
     *
     * The bad block table memory is allocated here. It must be freed
     * by calling the nand_free_bbt function.
     *
    */
    int nand_scan_bbt (struct mtd_info *mtd, struct nand_bbt_descr *bd)
    {
        struct nand_chip *this = mtd->priv;
        int len, res = 0;
        uint8_t *buf;
        struct nand_bbt_descr *td = this->bbt_td;
        struct nand_bbt_descr *md = this->bbt_md;

        len = mtd->size >> (this->bbt_erase_shift + 2);
        /* Allocate memory (2bit per block) */
        /* 2bit per block=(2/8)byte per block,所以上面要多右移2位 */
        this->bbt = kmalloc (len, GFP_KERNEL);
        if (!this->bbt) {
            printk (KERN_ERR "nand_scan_bbt: Out of memory\n");
            return -ENOMEM;
        }
        /* Clear the memory bad block table */
        memset (this->bbt, 0x00, len);

        /* If no primary table decriptor is given, scan the device
         * to build a memory based bad block table
         */
        if (!td) {
            if ((res = nand_memory_bbt(mtd, bd))) {
                printk (KERN_ERR "nand_bbt: Can't scan flash and build the RAM-based BBT\n");
                kfree (this->bbt);
                this->bbt = NULL;
            }
            return res;
        }

        /* Allocate a temporary buffer for one eraseblock incl. oob */
        /* 分配1 block所需要的oob data空间 */
        len = (1 << this->bbt_erase_shift);
        len += (len >> this->page_shift) * mtd->oobsize;
        buf = kmalloc (len, GFP_KERNEL);
        if (!buf) {
            printk (KERN_ERR "nand_bbt: Out of memory\n");
            kfree (this->bbt);
            this->bbt = NULL;
            return -ENOMEM;
        }
        
        //由于td、md均为NULL,一下函数基本不起作用,先不去研究它
        /* Is the bbt at a given page ? */
        if (td->options & NAND_BBT_ABSPAGE) {
            res = read_abs_bbts (mtd, buf, td, md);
        } else {    
            /* Search the bad block table using a pattern in oob */
            res = search_read_bbts (mtd, buf, td, md);
        }    

        if (res) 
            res = check_create (mtd, buf, bd);
        
        /* Prevent the bbt regions from erasing / writing */
        mark_bbt_region (mtd, td);
        if (md)
            mark_bbt_region (mtd, md);
        
        kfree (buf);
        return res;
    }

    /**
     * nand_memory_bbt - [GENERIC] create a memory based bad block table
     * @mtd:    MTD device structure
     * @bd:        descriptor for the good/bad block search pattern
     *
     * The function creates a memory based bbt by scanning the device 
     * for manufacturer / software marked good / bad blocks
    */
    static inline int nand_memory_bbt (struct mtd_info *mtd, struct nand_bbt_descr *bd)
    {
        struct nand_chip *this = mtd->priv;

        bd->options &= ~NAND_BBT_SCANEMPTY;
        //我们只需要扫描oob data,不需要扫描全部(512+16bytes的数据)
        return create_bbt (mtd, this->data_buf, bd, -1);
    }

    /**
     * create_bbt - [GENERIC] Create a bad block table by scanning the device
     * @mtd:    MTD device structure
     * @buf:    temporary buffer
     * @bd:        descriptor for the good/bad block search pattern
     * @chip:    create the table for a specific chip, -1 read all chips.
     *        Applies only if NAND_BBT_PERCHIP option is set
     *
     * Create a bad block table by scanning the device
     * for the given good/bad block identify pattern
     */
    static int create_bbt (struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd, int chip)
    {
        struct nand_chip *this = mtd->priv;
        int i, j, numblocks, len, scanlen;
        int startblock;
        loff_t from;
        size_t readlen, ooblen;

        printk (KERN_INFO "Scanning device for bad blocks\n");

        if (bd->options & NAND_BBT_SCANALLPAGES)//扫描所有都页
            len = 1 << (this->bbt_erase_shift - this->page_shift);//求出每block所含的page数
        else {
            if (bd->options & NAND_BBT_SCAN2NDPAGE)//只检查2 page
                len = 2;
            else    
                len = 1;//只检查1 page
        }

        if (!(bd->options & NAND_BBT_SCANEMPTY)) {
            /* We need only read few bytes from the OOB area */
            /* 我们只需要检查OOB的某些数据 */
            scanlen = ooblen = 0;
            readlen = bd->len;
        } else {
            /* Full page content should be read */
            /* 读取整页内容 */
            scanlen    = mtd->oobblock + mtd->oobsize;
            readlen = len * mtd->oobblock;
            ooblen = len * mtd->oobsize;
        }

        if (chip == -1) {
            /* Note that numblocks is 2 * (real numblocks) here, see i+=2 below as it
             * makes shifting and masking less painful */
            /* 计算出nand flash所包含都block数目(注意这里总数目经过林乘2操作)*/
            numblocks = mtd->size >> (this->bbt_erase_shift - 1);
            startblock = 0;
            from = 0;
        } else {
            if (chip >= this->numchips) {
                printk (KERN_WARNING "create_bbt(): chipnr (%d) > available chips (%d)\n",
                    chip + 1, this->numchips);
                return -EINVAL;
            }
            numblocks = this->chipsize >> (this->bbt_erase_shift - 1);
            startblock = chip * numblocks;
            numblocks += startblock;
            from = startblock << (this->bbt_erase_shift - 1);
        }
        
        for (i = startblock; i < numblocks;) {
            int ret;
            
            if (bd->options & NAND_BBT_SCANEMPTY)        //整页数据读取
                if ((ret = nand_read_raw (mtd, buf, from, readlen, ooblen)))
                    return ret;

            for (j = 0; j < len; j++) {
                if (!(bd->options & NAND_BBT_SCANEMPTY)) {
                    size_t retlen;
                    
                    /* Read the full oob until read_oob is fixed to 
                     * handle single byte reads for 16 bit buswidth */
                    /* 读取当前页的oob区的所有数据 */
                    ret = mtd->read_oob(mtd, from + j * mtd->oobblock,
                                mtd->oobsize, &retlen, buf);
                    if (ret)
                        return ret;
                    /* 检查oob data的bad block标志位,判断是否是坏块 */
                    if (check_short_pattern (buf, bd)) {
                        this->bbt[i >> 3] |= 0x03 << (i & 0x6);
                    /* 注意:这里i=实际值*2。由于一个block的状态用2bit来表示,那么一个字节可以存放4个block的状态。
                       这里i>>3刚好是实际block/4,4个block的状态刚好存放在this->bbt所指向的一个字节里面 
                     */
                        printk (KERN_WARNING "Bad eraseblock %d at 0x%08x\n", 
                            i >> 1, (unsigned int) from);
                        break;
                    }
                } else {
                    if (check_pattern (&buf[j * scanlen], scanlen, mtd->oobblock, bd)) {
                        this->bbt[i >> 3] |= 0x03 << (i & 0x6);
                        printk (KERN_WARNING "Bad eraseblock %d at 0x%08x\n", 
                            i >> 1, (unsigned int) from);
                        break;
                    }
                }
            }
            i += 2;//更新block的序号
            from += (1 << this->bbt_erase_shift);//更新nand flash的地址
        }
        return 0;
    }

    /**
     * nand_release - [NAND Interface] Free resources held by the NAND device 
     * @mtd:    MTD device structure
    */
    void nand_release (struct mtd_info *mtd)
    {
        struct nand_chip *this = mtd->priv;

    #ifdef CONFIG_MTD_PARTITIONS
        /* Deregister partitions */
        del_mtd_partitions (mtd);
    #endif
        /* Deregister the device */
        del_mtd_device (mtd);

        /* Free bad block table memory, if allocated */
        if (this->bbt)
            kfree (this->bbt);
        /* Buffer allocated by nand_scan ? */
        if (this->options & NAND_OOBBUF_ALLOC)
            kfree (this->oob_buf);
        /* Buffer allocated by nand_scan ? */
        if (this->options & NAND_DATABUF_ALLOC)
            kfree (this->data_buf);
    }

    附录:
    /arch/arm/mach-s3c2410/dev.c文件:

    static struct mtd_partition partition_info[]={
      [0]={
         name    :"vivi",
         size    :0x20000,
         offset  :0,
      },[1]={
         name    :"param",
         size    :0x10000,
         offset  :0x20000,
      },[2]={
         name    :"kernel",
         size    :0x1d0000,
         offset  :0x30000,
      },[3]={
         name    :"root",
         size    :0x3c00000,
         offset  :0x200000,
      }
    };

    struct s3c2410_nand_set nandset={
        nr_partitions    :4,
        partitions       :partition_info,
    };

    struct s3c2410_platform_nand superlpplatform={
        tacls     :0,
        twrph0    :30,
        twrph1    :0,
        sets      :&nandset,
        nr_sets   :1,
    };

    struct platform_device s3c_device_nand = {
        .name          = "s3c2410-nand",
        .id          = -1,
        .num_resources      = ARRAY_SIZE(s3c_nand_resource),
        .resource      = s3c_nand_resource,
        .dev={
            .platform_data=&superlpplatform
        }
    };

    nand_flash_ids表
    /driver/mtd/nand/nand_ids.c文件:
    struct nand_flash_dev nand_flash_ids[] = {
    ................................................................................
        {"NAND 64MiB 3,3V 8-bit",     0x76, 512, 64, 0x4000, 0},
    ................................................................................
    };
    注:
        这里只列出常用的samsun 64M Nand Flash的资料,对应的信息请看该结构体的定义:
    struct nand_flash_dev {
        char *name;
        int id;
        unsigned long pagesize;
        unsigned long chipsize;
        unsigned long erasesize;
        unsigned long options;
    };
    可知该nand flash 设备ID号为0x76,页大小为512,大小为64(M),檫除单元大小为16(K)。

    现在再由上到下的研究一下是如何通过MTD原始设备来访问FLASH硬件驱动的。

    首先分析一下如何通过MTD原始设备进而通过FLASH硬件驱动来读取FLASH存储器的数据。

    引用自<<Linux系统移植>>一文:

    "读Nand Flash:
    当对nand flash的设备文件(nand flash在/dev下对应的文件)执行系统调用read(),或在某个文件系统中对该
    设备进行读操作时. 会调用struct mtd_info中的read方法,他们缺省调用函数为nand_read(),在
    drivers/mtd/nand/nand_base.c中定义.nand_read()调用nand_do_read_ecc(),执行读操作. 在
    nand_do_read_ecc()函数中,主要完成如下几项工作:
    1. 会调用在nand flash驱动中对struct nand_chip重载的select_chip方法,即
    s3c2410_nand_select_chip()选择要操作的MTD芯片.
    2. 会调用在struct nand_chip中系统缺省的方法cmdfunc发送读命令到nand flash.
    3. 会调用在nand flash驱动中对struct nand_chip重载的read_buf(),即s3c2410_nand_read_buf()
    从Nand Flash的控制器的数据寄存器中读出数据.
    4. 如果有必要的话,会调用在nand flash驱动中对struct nand_chip重载的
    enable_hwecc,correct_data以及calculate_ecc方法,进行数据ECC校验。"

    下面研究一下其中的细节:
    /**
     * nand_read - [MTD Interface] MTD compability function for nand_do_read_ecc
     * @mtd:    MTD device structure
     * @from:    offset to read from
     * @len:    number of bytes to read
     * @retlen:    pointer to variable to store the number of read bytes
     * @buf:    the databuffer to put data
     *
     * This function simply calls nand_do_read_ecc with oob buffer and oobsel = NULL
     * and flags = 0xff
     */
    static int nand_read (struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf)
    {
        return nand_do_read_ecc (mtd, from, len, retlen, buf, NULL, &mtd->oobinfo, 0xff);
    }
    注:
        以参数oob_buf为NULL,flags为0xff调用nand_do_read_ecc函数。

    /**
     * nand_do_read_ecc - [MTD Interface] Read data with ECC
     * @mtd:    MTD device structure
     * @from:    offset to read from
     * @len:    number of bytes to read
     * @retlen:    pointer to variable to store the number of read bytes
     * @buf:    the databuffer to put data
     * @oob_buf:    filesystem supplied oob data buffer (can be NULL)
     * @oobsel:    oob selection structure
     * @flags:    flag to indicate if nand_get_device/nand_release_device should be preformed
     *        and how many corrected error bits are acceptable:
     *          bits 0..7 - number of tolerable errors
     *          bit  8    - 0 == do not get/release chip, 1 == get/release chip
     *
     * NAND read with ECC
     */
    int nand_do_read_ecc (struct mtd_info *mtd, loff_t from, size_t len,
                     size_t * retlen, u_char * buf, u_char * oob_buf, 
                     struct nand_oobinfo *oobsel, int flags)
    {

        int i, j, col, realpage, page, end, ecc, chipnr, sndcmd = 1;
        int read = 0, oob = 0, ecc_status = 0, ecc_failed = 0;
        struct nand_chip *this = mtd->priv;
        u_char *data_poi, *oob_data = oob_buf;//目前oob_data指针为空,以后会去修改它。
        u_char ecc_calc[32];//该数组用于存放计算出来的ecc结果
        u_char ecc_code[32];//该数组用于存放oob中ecc部分的数据
        int eccmode, eccsteps;//eccmode存放ecc的类型(ECC_SOFT);
                                eccsteps用于记录一个page所需的ecc校验次数(2)。
        int    *oob_config, datidx;
        int    blockcheck = (1 << (this->phys_erase_shift - this->page_shift)) - 1;
        int    eccbytes;
        int    compareecc = 1;//是否需要ecc标志(如果设置成ECC_NONE,这个标志将被清0)
        int    oobreadlen;


        DEBUG (MTD_DEBUG_LEVEL3, "nand_read_ecc: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);

        /* Do not allow reads past end of device */
        /* 不允许超越设备容量的读操作 */
        if ((from + len) > mtd->size) {
            DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: Attempt read beyond end of device\n");
            *retlen = 0;
            return -EINVAL;
        }

        /* Grab the lock and see if the device is available */
        /* 获取自旋锁,等待设备可用并获取其控制权 */
        if (flags & NAND_GET_DEVICE)
            nand_get_device (this, mtd, FL_READING);

        /* Autoplace of oob data ? Use the default placement scheme */
        if (oobsel->useecc == MTD_NANDECC_AUTOPLACE)
            oobsel = this->autooob;
        /* 
         * 感觉这一步有点多余,因为nand_scan中已经调用了以下代码: 
         * memcpy(&mtd->oobinfo, this->autooob, sizeof(mtd->oobinfo));
         * 把this->autooob的内容拷贝到mtd->oobinfo中了
         */
            
        eccmode = oobsel->useecc ? this->eccmode : NAND_ECC_NONE;
        oob_config = oobsel->eccpos;//记录ecc在oob数据中的位置

        /* Select the NAND device */
        chipnr = (int)(from >> this->chip_shift);
        this->select_chip(mtd, chipnr);//选择nand flash芯片(在s3c2410 nand flash控制器中为空操作)

        /* First we calculate the starting page */
        /* 首先,我们计算出开始页码 */
        realpage = (int) (from >> this->page_shift);
        page = realpage & this->pagemask;

        /* Get raw starting column */
        /* 其次,我们计算页内偏址 */
        col = from & (mtd->oobblock - 1);

        end = mtd->oobblock;//页大小(512)
        ecc = this->eccsize;//ecc保护下的数据大小(256)
        eccbytes = this->eccbytes;//ecc所占的字节数(3)
        
        if ((eccmode == NAND_ECC_NONE) || (this->options & NAND_HWECC_SYNDROME))
            compareecc = 0;//如果设置为关闭ECC或写操作才需要ECC,那把ecc给禁用(现在可是读操作^_^)

        oobreadlen = mtd->oobsize;//16
        if (this->options & NAND_HWECC_SYNDROME) 
            oobreadlen -= oobsel->eccbytes;

        /* Loop until all data read */
        while (read < len) {
            
            int aligned = (!col && (len - read) >= end);
            /* 
             * If the read is not page aligned, we have to read into data buffer
             * due to ecc, else we read into return buffer direct
             * 如果要读的位置不是页对齐都话,那么只要先把整页读出来,
             * 取出所需要读取的数据,然后修改读位置,那么以后的读操作都是页对齐的了。
             */
            if (aligned)
                data_poi = &buf[read];
            else 
                data_poi = this->data_buf;
            
            /* Check, if we have this page in the buffer 
             *
             * FIXME: Make it work when we must provide oob data too,
             * check the usage of data_buf oob field
             * 如果我们所需要的数据还存在于缓冲中都话:
             * 1 如果读位置页对齐,我们只要把缓冲中的数据直接拷贝到data_poi(buf[read])中即可(因为数据存在与缓存中,所以也无需要考虑ecc问题)
             * 2 如果读位置不是页对齐,什么读不要作,让其继续留在缓存(data_buf)中,以后会从data_poi(指向缓存data_buf)中提取所需要的数据。
             */
            if (realpage == this->pagebuf && !oob_buf) {
                /* aligned read ? */
                if (aligned)
                    memcpy (data_poi, this->data_buf, end);
                goto readdata;
            }

            /* Check, if we must send the read command */
            /* 发送读命令,页地址为page,列地址为0x00 */
            if (sndcmd) {
                this->cmdfunc (mtd, NAND_CMD_READ0, 0x00, page);
                sndcmd = 0;
            }    

            /* get oob area, if we have no oob buffer from fs-driver */
            if (!oob_buf || oobsel->useecc == MTD_NANDECC_AUTOPLACE ||
                oobsel->useecc == MTD_NANDECC_AUTOPL_USR)
                oob_data = &this->data_buf[end];//以上情况,oob_data暂存在data_buf缓存中

            eccsteps = this->eccsteps;//2
            
            switch (eccmode) {
            case NAND_ECC_NONE: {    /* No ECC, Read in a page */
                static unsigned long lastwhinge = 0;
                if ((lastwhinge / HZ) != (jiffies / HZ)) {
                    printk (KERN_WARNING "Reading data from NAND FLASH without ECC is not recommended\n");
                    lastwhinge = jiffies;
                }
                this->read_buf(mtd, data_poi, end);
                break;
            }
                
            case NAND_ECC_SOFT:    /* Software ECC 3/256: Read in a page + oob data */
                this->read_buf(mtd, data_poi, end);//读取数据到data_poi
                for (i = 0, datidx = 0; eccsteps; eccsteps--, i+=3, datidx += ecc) 
                    this->calculate_ecc(mtd, &data_poi[datidx], &ecc_calc[i]);
                /* 计算出读取到data_poi的数据的ecc值,并存放到ecc_calc数组中。
                 * 因为读都数据有一页大小(512),需要分别对其上半部和下半部分计算一次ecc值,并分开存放到ecc_calc数组相应都位置中。
                 */
                break;    

            default:
                for (i = 0, datidx = 0; eccsteps; eccsteps--, i+=eccbytes, datidx += ecc) {
                    this->enable_hwecc(mtd, NAND_ECC_READ);
                    this->read_buf(mtd, &data_poi[datidx], ecc);

                    /* HW ecc with syndrome calculation must read the
                     * syndrome from flash immidiately after the data */
                    if (!compareecc) {
                        /* Some hw ecc generators need to know when the
                         * syndrome is read from flash */
                        this->enable_hwecc(mtd, NAND_ECC_READSYN);
                        this->read_buf(mtd, &oob_data[i], eccbytes);
                        /* We calc error correction directly, it checks the hw
                         * generator for an error, reads back the syndrome and
                         * does the error correction on the fly */
                        ecc_status = this->correct_data(mtd, &data_poi[datidx], &oob_data[i], &ecc_code[i]);
                        if ((ecc_status == -1) || (ecc_status > (flags && 0xff))) {
                            DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: " 
                                "Failed ECC read, page 0x%08x on chip %d\n", page, chipnr);
                            ecc_failed++;
                        }
                    } else {
                        this->calculate_ecc(mtd, &data_poi[datidx], &ecc_calc[i]);
                    }    
                }
                break;                        
            }

            /* read oobdata */
            this->read_buf(mtd, &oob_data[mtd->oobsize - oobreadlen], oobreadlen);
            //读取oob_data存放到oob_data[mtd->oobsize - oobreadlen],在这里是data_buf[end]中

            /* Skip ECC check, if not requested (ECC_NONE or HW_ECC with syndromes) */
            /* 跳过ecc检测 */
            if (!compareecc)
                goto readoob;    
            
            /* Pick the ECC bytes out of the oob data */
            /* 从刚读出来都oob_data中取出ecc数据(在这里是前三个字节) */
            for (j = 0; j < oobsel->eccbytes; j++)
                ecc_code[j] = oob_data[oob_config[j]];

            /* correct data, if neccecary */
            for (i = 0, j = 0, datidx = 0; i < this->eccsteps; i++, datidx += ecc) {
                ecc_status = this->correct_data(mtd, &data_poi[datidx], &ecc_code[j], &ecc_calc[j]);
                /* 拿前面计算出来都ecc_cal数组都数据与读出来的ecc数据作比较,并尝试修正错误(但不保证能修复,具体看返回值) */
                
                /* Get next chunk of ecc bytes */
                j += eccbytes;
                
                /* Check, if we have a fs supplied oob-buffer, 
                 * This is the legacy mode. Used by YAFFS1
                 * Should go away some day
                 */
                if (oob_buf && oobsel->useecc == MTD_NANDECC_PLACE) { 
                    int *p = (int *)(&oob_data[mtd->oobsize]);
                    p[i] = ecc_status;
                }
                /* 很不幸,ecc检测发现错误且未能修复,报告错误 */    
                if ((ecc_status == -1) || (ecc_status > (flags && 0xff))) {    
                    DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: " "Failed ECC read, page 0x%08x\n", page);
                    ecc_failed++;
                }
            }        

        readoob:
            /* check, if we have a fs supplied oob-buffer */
            if (oob_buf) {
                /* without autoplace. Legacy mode used by YAFFS1 */
                switch(oobsel->useecc) {
                case MTD_NANDECC_AUTOPLACE:
                case MTD_NANDECC_AUTOPL_USR:
                    /* Walk through the autoplace chunks */
                    for (i = 0; oobsel->oobfree[i][1]; i++) {
                        int from = oobsel->oobfree[i][0];
                        int num = oobsel->oobfree[i][1];
                        memcpy(&oob_buf[oob], &oob_data[from], num);
                        oob += num;
                    }
                    break;
                case MTD_NANDECC_PLACE:
                    /* YAFFS1 legacy mode */
                    oob_data += this->eccsteps * sizeof (int);
                default:
                    oob_data += mtd->oobsize;
                }
            }
        readdata:
            /* Partial page read, transfer data into fs buffer 
             * 读位置不是页对齐,从data_poi(data_buf中)提取所需要都数据
             */
            if (!aligned) { 
                for (j = col; j < end && read < len; j++)
                    buf[read++] = data_poi[j];//read自增
                this->pagebuf = realpage;    
            } else        
                read += mtd->oobblock;//整页读取,计数值加上整页的数目(512)

            /* Apply delay or wait for ready/busy pin 
             * Do this before the AUTOINCR check, so no problems
             * arise if a chip which does auto increment
             * is marked as NOAUTOINCR by the board driver.
            */
            if (!this->dev_ready) 
                udelay (this->chip_delay);
            else
                nand_wait_ready(mtd);
                
            if (read == len)//所需数据读完都情况,退出读循环
                break;    

            /* For subsequent reads align to page boundary. */
            col = 0;//对于读位置不是页对齐都情况,前面已对其进行林相应都处理,现在读位置变得页对齐了。
            /* Increment page address */
            realpage++;//页地址加1,读取下一页。

            page = realpage & this->pagemask;
            /* Check, if we cross a chip boundary */
            if (!page) {
                chipnr++;
                this->select_chip(mtd, -1);
                this->select_chip(mtd, chipnr);
            }
            /* Check, if the chip supports auto page increment 
             * or if we have hit a block boundary. 
             * 如果芯片支持页自增操作,且未到block boundary(15)的话,不用再发送读命令
            */ 
            if (!NAND_CANAUTOINCR(this) || !(page & blockcheck))
                sndcmd = 1;                
        }

        /* Deselect and wake up anyone waiting on the device */
        if (flags & NAND_GET_DEVICE)
            nand_release_device(mtd);//放弃对设备都控制权,好让其它进程获取并占有它

        /*
         * Return success, if no ECC failures, else -EBADMSG
         * fs driver will take care of that, because
         * retlen == desired len and result == -EBADMSG
         */
        *retlen = read;
        return ecc_failed ? -EBADMSG : 0;
    }

    好的,接着研究一下如何通过MTD原始设备进而通过FLASH硬件驱动向FLASH存储器写数据。

    引用自<<Linux系统移植>>一文:

    写Nand Flash
    当对nand flash的设备文件(nand flash在/dev下对应的文件)执行系统调用write(),或在某个文件系统中对该设备
    进行读操作时, 会调用struct mtd_info中write方法,他们缺省调用函数为nand_write(),这两个函数在
    drivers/mtd/nand/nand_base.c中定义. nand_write()调用nand_write_ecc(),执行写操作.在
    nand_do_write_ecc()函数中,主要完成如下几项工作:
    1. 会调用在nand flash驱动中对struct nand_chip重载的select_chip方法,即
    s3c2410_nand_select_chip()选择要操作的MTD芯片.
    2. 调用nand_write_page()写一个页.
    3. 在nand_write_page()中,会调用在struct nand_chip中系统缺省的方法cmdfunc发送写命令
    到nand flash.
    4. 在nand_write_page()中,会调用在nand flash驱动中对struct nand_chip重载的
    write_buf(),即s3c2410_nand_write_buf()从Nand Flash的控制器的数据寄存器中写入数据.
    5. 在nand_write_page()中,会调用在nand flash驱动中对struct nand_chip重载waitfunc方法,
    该方法调用系统缺省函数nand_wait(),该方法获取操作状态,并等待nand flash操作完成.等
    待操作完成,是调用nand flash驱动中对struct nand_chip中重载的dev_ready方法,即
    s3c2410_nand_devready()函数.

    下面研究一下其中的细节:
    /**
     * nand_write - [MTD Interface] compability function for nand_write_ecc
     * @mtd:    MTD device structure
     * @to:        offset to write to
     * @len:    number of bytes to write
     * @retlen:    pointer to variable to store the number of written bytes
     * @buf:    the data to write
     *
     * This function simply calls nand_write_ecc with oob buffer and oobsel = NULL
     *
    */
    static int nand_write (struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf)
    {
        return (nand_write_ecc (mtd, to, len, retlen, buf, NULL, NULL));
    }
    注:
        以参数eccbuf、oobsel为NULL,调用nand_write_ecc函数。

    /**
     * nand_write_ecc - [MTD Interface] NAND write with ECC
     * @mtd:    MTD device structure
     * @to:        offset to write to
     * @len:    number of bytes to write
     * @retlen:    pointer to variable to store the number of written bytes
     * @buf:    the data to write
     * @eccbuf:    filesystem supplied oob data buffer
     * @oobsel:    oob selection structure
     *
     * NAND write with ECC
     */
    static int nand_write_ecc (struct mtd_info *mtd, loff_t to, size_t len,
                 size_t * retlen, const u_char * buf, u_char * eccbuf, struct nand_oobinfo *oobsel)
    {
        int startpage, page, ret = -EIO, oob = 0, written = 0, chipnr;
        int autoplace = 0, numpages, totalpages;
        struct nand_chip *this = mtd->priv;
        u_char *oobbuf, *bufstart;
        int    ppblock = (1 << (this->phys_erase_shift - this->page_shift));//page/block

        DEBUG (MTD_DEBUG_LEVEL3, "nand_write_ecc: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);

        /* Initialize retlen, in case of early exit */
        *retlen = 0;

        /* Do not allow write past end of device */
        /* 超越nand flash容量的写操作是不允许的 */
        if ((to + len) > mtd->size) {
            DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: Attempt to write past end of page\n");
            return -EINVAL;
        }

        /* reject writes, which are not page aligned */
        /* 不按页对齐的写操作同样是不允许的 */   

        if (NOTALIGNED (to) || NOTALIGNED(len)) {
            printk (KERN_NOTICE "nand_write_ecc: Attempt to write not page aligned data\n");
            return -EINVAL;
        }

        /* Grab the lock and see if the device is available */
        /* 获取设备的控制权 */
        nand_get_device (this, mtd, FL_WRITING);

        /* Calculate chipnr */
        /* 
         * 存在多片flash的情况下,计算出所要写的是哪片flash?
         * (当然,像我的板,只用一片nand flash,所以这个操作是不必要的)
         */
        chipnr = (int)(to >> this->chip_shift);

        /* Select the NAND device */
        /* 片选操作 */
        this->select_chip(mtd, chipnr);

        /* Check, if it is write protected */
        /* 如果nand flash写保护,当然不能再写了 */
        if (nand_check_wp(mtd))
            goto out;

        /* if oobsel is NULL, use chip defaults */
        if (oobsel == NULL) 
            oobsel = &mtd->oobinfo;        
            
        /* Autoplace of oob data ? Use the default placement scheme */
        if (oobsel->useecc == MTD_NANDECC_AUTOPLACE) {
            oobsel = this->autooob;
            autoplace = 1;
        }    
        if (oobsel->useecc == MTD_NANDECC_AUTOPL_USR)
            autoplace = 1;

        /* Setup variables and oob buffer */
        totalpages = len >> this->page_shift;//计算所要读取的数据长度共有多少页
        page = (int) (to >> this->page_shift);//计算数据所要写到的开始页码
        /* Invalidate the page cache, if we write to the cached page */
        /* 如果缓存保存的数据在我们要写数据的范围内,把缓存里的数据设置为不可用???? */
        if (page <= this->pagebuf && this->pagebuf < (page + totalpages))  
            this->pagebuf = -1;
        
        /* Set it relative to chip */
        page &= this->pagemask;
        startpage = page;
        /* Calc number of pages we can write in one go */
        numpages = min (ppblock - (startpage  & (ppblock - 1)), totalpages);//计算出本block中允许被写的页数
        oobbuf = nand_prepare_oobbuf (mtd, eccbuf, oobsel, autoplace, numpages);//先不深入研究~_~
        bufstart = (u_char *)buf;//获取所要写数据的地址

        /* Loop until all data is written */
        /* 循环进行写操作 */
        while (written < len) {

            this->data_poi = (u_char*) &buf[written];//先把所要写的数据缓冲到data_poi下
            /* Write one page. If this is the last page to write
             * or the last page in this block, then use the
             * real pageprogram command, else select cached programming
             * if supported by the chip.
             * 如果这是所写数据的最后一个页或许这是所写block的最后一个页,调用nand flash的
              
             * pageprogram指令,真正把数据写入nand flash中(nand flash的最小擦除单元为block)
             */
            ret = nand_write_page (mtd, this, page, &oobbuf[oob], oobsel, (--numpages > 0));
            if (ret) {
                DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: write_page failed %d\n", ret);
                goto out;
            }    
            /* Next oob page */
            oob += mtd->oobsize;
            /* Update written bytes count */
            /* 更新写入计数值 */
            written += mtd->oobblock;
            if (written == len)//写入完毕,退出 
                goto cmp;
            
            /* Increment page address */
            page++;//下一页

            /* Have we hit a block boundary ? Then we have to verify and
             * if verify is ok, we have to setup the oob buffer for
             * the next pages.
             
    暂时不是很明白,需要先搞明白nand_prepare_oobbuf函数的作用
            */
            if (!(page & (ppblock - 1))){
                int ofs;
                this->data_poi = bufstart;//怀疑nand_verify_pages用到
                ret = nand_verify_pages (mtd, this, startpage, 
                    page - startpage,
                    oobbuf, oobsel, chipnr, (eccbuf != NULL));//一页写完,检查数据
                if (ret) {
                    DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: verify_pages failed %d\n", ret);
                    goto out;
                }    
                *retlen = written;

                ofs = autoplace ? mtd->oobavail : mtd->oobsize;
                if (eccbuf)
                    eccbuf += (page - startpage) * ofs;
                totalpages -= page - startpage;//更新需要写的页数
                numpages = min (totalpages, ppblock);//更新可以写的页数
                page &= this->pagemask;//更新页码
                startpage = page;//更新开始页码
                oobbuf = nand_prepare_oobbuf (mtd, eccbuf, oobsel, 
                        autoplace, numpages);
                /* Check, if we cross a chip boundary */
                if (!page) {
                    chipnr++;
                    this->select_chip(mtd, -1);
                    this->select_chip(mtd, chipnr);
                }
            }
        }
        /* Verify the remaining pages */
    cmp:
        this->data_poi = bufstart;//怀疑nand_verify_pages用到
         ret = nand_verify_pages (mtd, this, startpage, totalpages,
            oobbuf, oobsel, chipnr, (eccbuf != NULL));
        if (!ret)
            *retlen = written;
        else    
            DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: verify_pages failed %d\n", ret);

    out:
        /* Deselect and wake up anyone waiting on the device */
        nand_release_device(mtd);//放弃对设备的控制权

        return ret;
    }

    /**
     * nand_write_page - [GENERIC] write one page
     * @mtd:    MTD device structure
     * @this:    NAND chip structure
     * @page:     startpage inside the chip, must be called with (page & this->pagemask)
     * @oob_buf:    out of band data buffer
     * @oobsel:    out of band selecttion structre
     * @cached:    1 = enable cached programming if supported by chip
     *
     * Nand_page_program function is used for write and writev !
     * This function will always program a full page of data
     * If you call it with a non page aligned buffer, you're lost :)
     *
     * Cached programming is not supported yet.
     */
    static int nand_write_page (struct mtd_info *mtd, struct nand_chip *this, int page, 
        u_char *oob_buf,  struct nand_oobinfo *oobsel, int cached)
    {
        int     i, status;
        u_char    ecc_code[32];
        int    eccmode = oobsel->useecc ? this->eccmode : NAND_ECC_NONE;
        int      *oob_config = oobsel->eccpos;
        int    datidx = 0, eccidx = 0, eccsteps = this->eccsteps;
        int    eccbytes = 0;
        
        /* FIXME: Enable cached programming */
        cached = 0;//在高版本的内核下找到这样的解释:
        /*
         * Cached progamming disabled for now, Not sure if its worth the
         * trouble. The speed gain is not very impressive. (2.3->2.6Mib/s)
         */
        
        /* Send command to begin auto page programming */
        /* 发送页编程指令 */
        this->cmdfunc (mtd, NAND_CMD_SEQIN, 0x00, page);

        /* Write out complete page of data, take care of eccmode */
        switch (eccmode) {
        /* No ecc, write all */
        case NAND_ECC_NONE:
            printk (KERN_WARNING "Writing data without ECC to NAND-FLASH is not recommended\n");
            this->write_buf(mtd, this->data_poi, mtd->oobblock);
            break;
            
        /* Software ecc 3/256, write all */
        case NAND_ECC_SOFT:
            for (; eccsteps; eccsteps--) {
                this->calculate_ecc(mtd, &this->data_poi[datidx], ecc_code);//计算出一页的ecc数据
                for (i = 0; i < 3; i++, eccidx++)
                    oob_buf[oob_config[eccidx]] = ecc_code[i];//存放到ecc_code数组中
                datidx += this->eccsize;
            }
            this->write_buf(mtd, this->data_poi, mtd->oobblock);//调用FLASH硬件驱动层进行写操作
            break;
        default:
            eccbytes = this->eccbytes;
            for (; eccsteps; eccsteps--) {
                /* enable hardware ecc logic for write */
                this->enable_hwecc(mtd, NAND_ECC_WRITE);
                this->write_buf(mtd, &this->data_poi[datidx], this->eccsize);
                this->calculate_ecc(mtd, &this->data_poi[datidx], ecc_code);
                for (i = 0; i < eccbytes; i++, eccidx++)
                    oob_buf[oob_config[eccidx]] = ecc_code[i];
                /* If the hardware ecc provides syndromes then
                 * the ecc code must be written immidiately after
                 * the data bytes (words) */
                if (this->options & NAND_HWECC_SYNDROME)
                    this->write_buf(mtd, ecc_code, eccbytes);
                datidx += this->eccsize;
            }
            break;
        }
                                            
        /* Write out OOB data */
        if (this->options & NAND_HWECC_SYNDROME)
            this->write_buf(mtd, &oob_buf[oobsel->eccbytes], mtd->oobsize - oobsel->eccbytes);
        else 
            this->write_buf(mtd, oob_buf, mtd->oobsize);//写oob data,主要把上面计算的ecc值写进去

        /* Send command to actually program the data */
        this->cmdfunc (mtd, cached ? NAND_CMD_CACHEDPROG : NAND_CMD_PAGEPROG, -1, -1);

        if (!cached) {
            /* call wait ready function */
            status = this->waitfunc (mtd, this, FL_WRITING);//等待写入完成

            /* See if operation failed and additional status checks are available */
            if ((status & NAND_STATUS_FAIL) && (this->errstat)) {
                status = this->errstat(mtd, this, FL_WRITING, status, page);
            }

            /* See if device thinks it succeeded */
            if (status & NAND_STATUS_FAIL) {
                DEBUG (MTD_DEBUG_LEVEL0, "%s: " "Failed write, page 0x%08x, ", __FUNCTION__, page);
                return -EIO;
            }
        } else {
            /* FIXME: Implement cached programming ! */
            /* wait until cache is ready*/
            // status = this->waitfunc (mtd, this, FL_CACHEDRPG);//cached的写操作暂时没用
        }
        return 0;    
    }


    展开全文
  • Flash原理介绍

    千次阅读 2019-04-12 17:53:00
    本文详细地讲解了某种(NAND)Flash的基础知识,看后受益匪浅,在此谢过原作者,作为知识的传播者,是我的榜样。

    转载地址:http://www.cnblogs.com/sankye/articles/1638852.html

    硬件特性:

    【Flash的硬件实现机制】

    Flash全名叫做Flash Memory,属于非易失性存储设备(Non-volatile Memory Device),与此相对应的是易失性存储设备(Volatile Memory Device)。关于什么是非易失性/易失性,从名字中就可以看出,非易失性就是不容易丢失,数据存储在这类设备中,即使断电了,也不会丢失,这类设备,除了Flash,还有其他比较常见的入硬盘,ROM等,与此相对的,易失性就是断电了,数据就丢失了,比如大家常用的内存,不论是以前的SDRAM,DDR SDRAM,还是现在的DDR2,DDR3等,都是断电后,数据就没了。

    Flash的内部存储是MOSFET,里面有个悬浮门(Floating Gate),是真正存储数据的单元。

    在Flash之前,紫外线可擦除(uv-erasable)的EPROM,就已经采用用Floating Gate存储数据这一技术了。

    1.典型的Flash内存单元的物理结构

    数据在Flash内存单元中是以电荷(electrical charge) 形式存储的。存储电荷的多少,取决于图中的外部门(external gate)所被施加的电压,其控制了是向存储单元中冲入电荷还是使其释放电荷。而数据的表示,以所存储的电荷的电压是否超过一个特定的阈值Vth来表示。

     

    【SLC和MLC的实现机制】

    Nand Flash按照内部存储数据单元的电压的不同层次,也就是单个内存单元中,是存储1位数据,还是多位数据,可以分为SLC和MLC:

    1.       SLC,Single Level Cell:

    单个存储单元,只存储一位数据,表示成1或0.

    就是上面介绍的,对于数据的表示,单个存储单元中内部所存储电荷的电压,和某个特定的阈值电压Vth,相比,如果大于此Vth值,就是表示1,反之,小于Vth,就表示0.

    对于nand Flash的数据的写入1,就是控制External Gate去充电,使得存储的电荷够多,超过阈值Vth,就表示1了。而对于写入0,就是将其放电,电荷减少到小于Vth,就表示0了。

    关于为何Nand Flash不能从0变成1,我的理解是,物理上来说,是可以实现每一位的,从0变成1的,但是实际上,对于实际的物理实现,出于效率的考虑,如果对于,每一个存储单元都能单独控制,即,0变成1就是,对每一个存储单元单独去充电,所需要的硬件实现就很复杂和昂贵,同时,所进行对块擦除的操作,也就无法实现之前的,一闪而过的速度了,也就失去了Flash的众多特性了。

     

    2.       MLC,Multi Level Cell

    与SLC相对应,就是单个存储单元,可以存储多个位,比如2位,4位等。其实现机制,说起来比较简单,就是,通过控制内部电荷的多少,分成多个阈值,通过控制里面的电荷多少,而达到我们所需要的存储成不同的数据。比如,假设输入电压是Vin=4V(实际没有这样的电压,此处只是为了举例方便),那么,可以设计出2的2次方=4个阈值, 1/4 的Vin=1V,2/4的Vin=2V,3/4的Vin=3V,Vin=4V,分别表示2位数据00,01,10,11,对于写入数据,就是充电,通过控制内部的电荷的多少,对应表示不同的数据。

    对于读取,则是通过对应的内部的电流(与Vth成反比),然后通过一系列解码电路完成读取,解析出所存储的数据。这些具体的物理实现,都是有足够精确的设备和技术,才能实现精确的数据写入和读出的。

    单个存储单元可以存储2位数据的,称作2的2次方=4 Level Cell,而不是2 Level Cell,这点,之前差点搞晕了。。。,同理,对于新出的单个存储单元可以存储4位数据的,称作 2的4次方=16 Level Cell。

     

    【关于如何识别SLC还是MLC

    Nand Flash设计中,有个命令叫做Read ID,读取ID,意思是读取芯片的ID,就像大家的身份证一样,这里读取的ID中,是读取好几个字节,一般最少是4个,新的芯片,支持5个甚至更多,从这些字节中,可以解析出很多相关的信息,比如此Nand Flash内部是几个芯片(chip)所组成的,每个chip包含了几片(Plane),每一片中的页大小,块大小,等等。在这些信息中,其中有一个,就是识别此flash是SLC还是MLC。下面这个就是最常见的Nand Flash的datasheet中所规定的,第3个字节,3rd byte,所表示的信息,其中就有SLC/MLC的识别信息:

     

     

     

     

    Description

    I/O7

    I/O6

    I/O5 I/O4

    I/O3 I/O2

    I/O1 I/O0

    Internal

    Chip Number

    1

    2

    4

    8

     

     

     

     

    0    0

    0    1

    1    0

    1    1

    Cell Type

    2 Level Cell

    4 Level Cell

    8 Level Cell

    16 Level Cell

     

     

     

    0     0

    0     1

    1     0

    1     1

     

    Number of

    Simultaneously

    Programmed Pages

    1

    2

    4

    8

     

     

    0     0

    0     1

    1     0

    1     1

     

     

    Interleave Program

    Between multiple chips

    Not Support

    Support

     

    0

    1

     

     

     

    Cache Program

    Not Support

    Support

    0

    1

     

     

     

     

    表1.Nand Flash 第3个ID的含义

     

    【Nand Flash的物理存储单元的阵列组织结构】

    Nand flash的内部组织结构,此处还是用图来解释,比较容易理解:

    图2.Nand Flash物理存储单元的阵列组织结构

    上图是K9K8G08U0A的datasheet中的描述。

    简单解释就是:

    1.一个nand flash由很多个块(Block)组成,块的大小一般是128KB,256KB,512KB,此处是128KB。

    2.每个块里面又包含了很多页(page)。每个页的大小,对于现在常见的nand flash多数是2KB,更新的nand flash是4KB,这类的,页大小大于2KB的nand flash,被称作big block,对应的发读写命令地址,一共5个周期(cycle),而老的nand flash,页大小是256B,512B,这类的nand flash被称作small block,。地址周期只有4个。

    而块,也是Nand Flash的擦除操作的基本/最小单位。

    3.每一个页,对应还有一块区域,叫做空闲区域(spare area)/冗余区域(redundant area),而Linux系统中,一般叫做OOB(Out Of Band),这个区域,是最初基于Nand Flash的硬件特性:数据在读写时候相对容易错误,所以为了保证数据的正确性,必须要有对应的检测和纠错机制,此机制被叫做EDC(Error Detection Code)/ECC(Error Code Correction, 或者 Error Checking and Correcting),所以设计了多余的区域,用于放置数据的校验值。

    页是Nand Flash的写入操作的基本/最小的单位。

     

    【Nand Flash数据存储单元的整体架构】

    简单说就是,常见的nand flash,内部只有一个chip,每个chip只有一个plane。

    而有些复杂的,容量更大的nand flash,内部有多个chip,每个chip有多个plane。这类的nand flash,往往也有更加高级的功能,比如下面要介绍的Multi Plane Program和Interleave Page Program等。

    比如,型号为K9K8G08U0A这个芯片(chip),内部有两个K9F4G08U0A,每个K9F4G08U0A包含了2个Plane,每个Plane是1Gb,所以K9F4G08U0A的大小是1Gb×2=2Gb=256MB,因此,K9K8G08U0A内部有2个K9F4G08U0A,即4个Plane,总大小是4×256MB=1GB。

    而型号是K9WAG08U1A的nand flash,内部包含了2个K9K8G08U0A,所以,总容量是K9K8G08U0A的两倍=1GB×2=2GB,类似地K9NBG08U5A,内部包含了4个K9K8G08U0A,总大小就是4×1GB=4GB。

     

    【Flash名称的由来】

    Flash的擦除操作是以block块为单位的,与此相对应的是其他很多存储设备,是以bit位为最小读取/写入的单位,Flash是一次性地擦除整个块:在发送一个擦除命令后,一次性地将一个block,常见的块的大小是128KB/256KB。。,全部擦除为1,也就是里面的内容全部都是0xFF了,由于是一下子就擦除了,相对来说,擦除用的时间很短,可以用一闪而过来形容,所以,叫做Flash Memory。中文有的翻译为 (快速)闪存。

     

    【Flash相对于普通设备的特殊性】

    1.       上面提到过的,Flash最小操作单位,有些特殊。

    一般设备,比如硬盘/内存,读取和写入都是以bit位为单位,读取一个bit的值,将某个值写入对应的地址的位,都是可以按位操作的。

    但是Flash由于物理特性,使得内部存储的数据,只能从1变成0,这点,可以从前面的内部实现机制了解到,只是方便统一充电,不方便单独的存储单元去放电,所以才说,只能从1变成0,也就是释放电荷。

    所以,总结一下Flash的特殊性如下:

     

     

    普通设备(硬盘/内存等)

    Flash

    读取/写入的叫法

    读取/写入

    读取/编程(Program)①

    读取/写入的最小单位

    Bit/位

    Page/页

    擦除(Erase)操作的最小单位

    Bit/位

    Block/块 ②

    擦除操作的含义

    将数据删除/全部写入0

    将整个块都擦除成全是1,也就是里面的数据都是0xFF ③

    对于写操作

    直接写即可

    在写数据之前,要先擦除,然后再写

    表2.Flash和普通设备相比所具有的特殊性

    注:

    ① 之所以将写操作叫做编程,是因为,flash 和之前的EPROM,EEPROM继承发展而来,而之前的EEPROM(Electrically Erasable Programmable Read-Only Memory),往里面写入数据,就叫做编程Program,之所以这么称呼,是因为其对数据的写入,是需要用电去擦除/写入的,就叫做编程。

    ② 对于目前常见的页大小是2K/4K的Nand Flash,其块的大小有128KB/256KB/512KB等。而对于Nor Flash,常见的块大小有64K/32K等。

    ③在写数据之前,要先擦除,内部就都变成0xFF了,然后才能写入数据,也就是将对应位由1变成0。

     

     

     

    展开全文
  • flash 鸟的flash动画

    热门讨论 2012-03-04 09:41:37
    flash
  • Flash,RAM,ROM区别

    万次阅读 多人点赞 2018-08-01 18:26:44
    本文转载自:终于有人说清楚了什么是DRAM、什么是NAND Flash 所有使用者对“存储器”这个名词可是一点都不陌生,因为所有的电子产品都必须用到存储器,且通常用到不只一种存储器。不过对于存储器种类、规格与形式...

    本文转载自:终于有人说清楚了什么是DRAM、什么是NAND Flash

     

    所有使用者对“存储器”这个名词可是一点都不陌生,因为所有的电子产品都必须用到存储器,且通常用到不只一种存储器。不过对于存储器种类、规格与形式,很多人容易搞混。比如,最近价格贵到炸的 NAND Flash,产业新闻里常常提到的DRAM,还有SRAM、SDRAM、DDR 3、DDR 4、NOR Flash … 这些又是什么?

    先来一段百度百科。

    存储器是用来存储程序和数据的部件,对于计算机来说,有了存储器,才有记忆功能,才能保证正常工作。存储器的种类很多,按其用途可分为主存储器和辅助存储器,主存储器又称内存储器(简称内存,港台称之为记忆体)。外储存器是指除计算机内存及CPU缓存以外的储存器,此类储存器一般断电后仍然能保存数据。常见的外存储器有硬盘、软盘、光盘、U盘等。

    而简单来说,DRAM就是我们一般在用的内存,而NAND Flash 闪存,它在做的事情其实是硬盘。

    (这段是给电脑小白的科普,大家可以酌情跳过)

    不熟悉PC知识的朋友常常在选购设备时问,硬盘和内存到底有什么差别?我硬盘容量明明有 1TB,但PC还是跑得很慢哎?

    硬盘和内存的差异,在于把电源关掉后、空间中储存的数据还会不会留着。就算关掉电源,硬盘的数据也不会消失。

    但我们要运算数据时,如果 CPU 要直接从硬盘里面抓数据,时间会太久。所以”内存”会作为中间桥梁,先到硬盘里面复制一份进来、再让 CPU 直接到内存中拿数据做运算。这样会 比直接去硬盘抓数据,快约数百万倍。

    打开任务管理器,就可以看到现在执行中程序占掉的内存空间,很多人就在骂Chrome 耗费的运算资源很高,内存使用率高于其他浏览器,多开几个分页内存就被吃完了。

    所以简单来说,计算机在运作就像是办公一样,喝饮料、看书本、听音响… 想一次使用越多东西、桌面(内存)就要越大。但其他一时间没有要用到的东西,都会放在抽屉(硬盘)里面。所以硬盘就算再大,你一次想执行很多任务,还是得要看内存大小。

    内存的处理速度比硬盘更快,但断电之后数据会消失,且价格也比硬盘贵。

    当然存储器的层次结构里面还有更多细节。参见后文。

    简单来说,CPU 里面也有一个储存空间,叫做 Register。要运算时、CPU 会从内存中把数据载入Register、再让Register中存的数字做运算,运算完再将结果存回内存中。毕竟 CPU 和内存终究还是两片不同的芯片,没有在同一片芯片里直接抓数据快。

    还有一个概念是 Cache,这是CPU 和内存之间的中间桥梁。

    速度来讲,就是:CPU里面的Register > Cache > 内存 > 硬盘。越上层(越靠近 CPU),速度就越快、价格越高、容量越低。

    存储器的分类

    电的存储器是指电写电读的存储器,主要分为两大类,如图一所示:

    易失性存储器(Volatile Memory,VM):电源开启时资料存在,电源关闭则资料立刻流失(资料挥发掉),例如:SRAM、DRAM、SDRAM、DDR-SDRAM 等。

    非易失性存储器(Non-Volatile Memory,NVM):电源开启时资料存在,电源关闭资料仍然可以保留,例如:ROM、PROM、EPROM、EEPROM、Flash ROM、FRAM、MRAM、RRAM、PCRAM 等。


    ▲ 图一:存储器的分类。

    存储器的单元

    存储器的“单元”(Cell)是指用来存取资料的最小结构,如果含有一个晶体管(Transistor)与一个电容(Capacitor)则称为“1T1C”;如果含有一个晶体管(Transistor)与一个电阻(Resistor)则称为“1T1R”;如果含有一个二极体(Diode)与一个电阻(Resistor)则称为“1D1R”。

    存储器的每个“单元”不一定只能储存 1 个位的资料,由于我们对存储器容量的要求越来越高,每个“单元”能储存的资料越来越多,依照每个“单元”能储存的资料位数又分为:单层单元(Single-Level Cell,SLC)、多层单元(Multi-Level Cell,MLC)、三层单元(Triple-Level Cell,TLC)、四层单元(Quad-Level Cell,QLC)等。

    存储器层次结构(Memory hierarchy)

    要了解电子产品的各种存储器配置,就必须先介绍“存储器层次结构”(Memory hierarchy)观念。存储器层次结构是指如何将储存容量不同、运算速度不同、单位价格不同的多种存储器妥善分配,才能达到最大的经济效益,使产品的运算速度合理、储存容量合理、产品价格合理。

    暂存器(Register,也译为寄存器):在处理器内,用来设定处理器的功能,主要是“暂时储存”设定值的地方。

    快取存储器(Cache memory,翻译版本有缓存,快取缓存区,快取存储器;台湾翻译为快取。):在处理器内,执行程序时“暂时储存”程序与资料的地方,通常以 SRAM 制作。

    主存储器(Main memory):在处理器外,“暂时储存”程序与资料的地方,通常以 DRAM 制作,目前已经改良成 SDRAM 或 DDR。

    辅助存储器(Assistant memory):在处理器外,“永久储存”程序与资料的地方,包括:快闪存储器、磁盘机、光盘机、磁带机等。

    不同种类的存储器分别有不同的储存容量、工作速度、单位价格:

    储存容量:辅助存储器(GB)> 主存储器(MB)> 快取存储器(KB)> 暂存器(B)。

    工作速度:辅助存储器(1ms)< 主存储器(10ns)< 快取存储器(1ns)< 暂存器(1ns)。

    单位价格:辅助存储器 < 主存储器 < 快取存储器 < 暂存器。

     

    存储器的应用

    所有的电子产品都必须用到存储器,而且通常用到不只一种存储器,由于存储器的种类繁多,常常让使用者混淆,我们简单说明不同存储器之间的差异,图三为手机主要芯片的系统方块图(System block diagram),包括:应用处理器(Application processor)、基带处理器(Baseband processor)、运动控制器(Motion Controller)。

    应用处理器主要是执行操作系统(Operating System,OS)与应用程序(Application program,App),暂存器与快取存储器目前都是内建在处理器内,其中暂存器用来设定处理器的功能,用来设定暂存器数值的程序,也就是用来趋动硬件的软件程序又称为“固件”(Firmware);快取存储器是在执行程序时用来“暂时储存”程序与资料的地方,由于在处理器内离运算单元比较近,可以缩短程序与资料来回的时间,加快程序的执行速度因此称为“Cache”。

    由于快取存储器成本较高因此容量不大,如果执行程序时放不下,则可以退一步放在主存储器内,可是目前主存储器所使用的 SDRAM 或 DDR,属于易失性存储器,电源关闭则资料立刻流失,因此关机后资料必须储存在非易失性的辅助存储器内,早期辅助存储器使用磁盘机、光盘机、磁带机等,由于半导体制程的进步,目前大多使用快闪存储器(Flash ROM),或所谓的固态硬盘(Solid State Disk,SSD),固态硬盘其实也是使快闪存储器制作。

    由于快取存储器(SRAM)与主存储器(SDRAM、DDR)是执行程序用来“暂时储存”程序与资料的地方,与处理器内的运算单位直接使用汇流排(Bus)连接,一般都是用“位”(bit)来计算容量;而辅助存储器是“永久储存”程序与资料的地方,由于一个位组(Byte)可以储存一个半型字,因此一般都是用“位组”(Byte)来计算容量。


    ▲ 图三:手机主要芯片的系统方块图(System block diagram)。

    静态随机存取存储器(SRAM:Static RAM)

    以 6 个晶体管(MOS)来储存 1 个位(1bit)的资料,而且使用时“不需要”周期性地补充电源来保持记忆的内容,故称为“静态”(Static)。

    SRAM 的构造较复杂(6 个晶体管储存 1 个位的资料),不使用电容所以存取速度较快,但是成本也较高,因此一般都制作成对容量要求较低但是对速度要求较高的存储器,例如:中央处理器(CPU)内建 256KB、512KB、1MB 的“快取存储器”(Cache memory),一般都是使用 SRAM。

    动态随机存取存储器(DRAM:Dynamic RAM)

    以一个晶体管(MOS)加上一个电容(Capacitor)来储存一个位(1bit)的资料,而且使用时“需要”周期性地补充电源来保持记忆的内容,故称为“动态”(Dynamic)。

    DRAM 构造较简单(一个晶体管加上一个电容),由于电容充电放电需要较长的时间造成存取速度较慢,但是成本也较低,因此一般制作成对容量要求较高但是对速度要求较低的存储器,例如:个人电脑主机板通常使用 1GB 以上的 DDR-SDRAM 就是属于一种 DRAM。由于处理器的速度越来越快,传统 DRAM 的速度已经无法满足要求,因此目前都改良成 SDRAM 或 DDR-SDRAM 等两种型式来使用。

    同步动态随机存取存储器(SDRAM:Synchronous DRAM)

    中央处理器(CPU)与主机板上的主存储器(SDRAM)存取资料时的“工作时脉”(Clock)相同,故称为“同步”(Synchronous)。由于 CPU 在存取资料时不需要“等待”(Wait)因此效率较高,SDRAM 的存取速度较 DRAM 快,所以早期电脑主机板上都是使用 SDRAM 来取代传统 DRAM,不过目前也只有少数工业电脑仍然使用 SDRAM。

    可以记住一个简单的结论:SRAM 比较快、 DRAM 比较慢;SRAM 比较贵、DRAM 比较便宜。

    这是我们平常在计算机中使用的内存,更精确的说法应该叫”内存模块”(Memory Module)。一个内存模块实际上就是由一块小电路板、再加上几块的 DRAM 芯片构成。图标中的内存模块上一共有 8 个 DRAM 芯片。让我们把一个 DRAM 芯片的内部结构剖开看看,会看到一个储存数组(Memorry Array)。

    CPU 会给这个储存数组”行地址”和”列地址”,就可以选出一个”储存单元”。常见的储存单元包含了 4 bit 或 8 bit,每一个 bit 都会采用一个电路结构,我们称为 DRAM 的一个”基本储存单元”。

    这个基本储存单元中包含了一个晶体管匹配一个电容。然后就可以视电容器是否有充电电荷存在、来判别目前的记忆状态。

    “写入内存”的动作,就是由外部的数据线、对电容进行充电或放电,从而完成写入 1 或 0 的数字数据。

    DRAM 使用一个晶体管(MOS)与一个电容来储存一个位的资料(一个 0 或一个 1),如图四(a)所示,当晶体管(MOS)不导通时没有电子流过,电容没有电荷,代表这一个位的资料是 0,如图四(b)所示;当晶体管(MOS)导通时(在闸极施加正电压),电子会由源极流向汲极,电容有电荷,代表这一个位的资料是 1,为了要将这些流过来的电荷“储存起来”,因此必须使用一个微小的电容,如图四(c)所示,DRAM 就是因为电容需要时间充电,所以速度比 SRAM 还慢。


    ▲ 图四:动态随机存取存储器(DRAM)的结构与工作原理示意图。

    由于电容会有漏电的现象,导致电位差不足而使记忆消失,因此除非电容经常周期性地充电,否则无法确保数据能长久保存起来。

    由于每个 DRAM 基本储存单元的电路结构非常的简单,所以功耗低、价格也较低。这样一来用低成本就能制造出大储存容量的 DRAM 芯片。缺点就是读写的速度慢(电容要充电放电),影响了 DRAM 的性能。

    SRAM 的结构则较为复杂,一共有六个晶体管构成。我们能分别用 M1、M2、M3 到 M6 进行标记。这六个晶体管合起来才能保存一个 bit。

     

    SRAM 芯片和 DRAM 芯片不太一样,不需要分成行地址和列地址分别选择,而且 SRAM 的设计相对来说又更加灵活,一个地址对应的储存单元数量可以是 8 bit、10 bit,或 32 bit、40 bit、64 bit 都行。

    另外,晶体管的开关速度远比电容充电放电的速度还快,所以相对于 DRAM、SRAM 的读写速度比 DRAM 快很多。

    然而 SRAM 中要储存一个 bit 就得用到六个晶体管。晶体管的数量一多、就会造成芯片的面积变大,从而带来集成电路难以变得更小、还有价格更贵的问题。

    (SRAM 的价格比起 DRMA 要高达 1000 倍以上。比如 2010 年世代––—SRAM 的每单位储存价格是 $60/MB,DRAM 则是 $0.06/MB。)

    同时每个晶体管都要耗电,晶体管越多、功耗就越高。考虑到价格高和功耗大,目前只能在一些很严苛的地方来使用 SRAM,比如上面提到的快取 (Cache)。

    故目前”主存储器”还是使用 DRAM 技术,但小块用来拉速度的”快取”就是采用 SRAM。然而无论是 DRAM 还是 SRAM,一不供应电源就会丧失储存的数据,所以都叫做挥发性内存。

    铁电随机存取存储器 FRAM

    动态随机存取存储器(DRAM)是以一个晶体管加上一个电容来储存一个位(1bit)的资料,由于传统 DRAM 的电容都是使用“氧化矽”做为绝缘体,氧化矽的介电常数不够大(K 值不够大),因此不容易吸引(储存)电子与电洞,造成必须不停地补充电子与电洞,所以称为“动态”,只要电脑的电源关闭,电容所储存的电子与电洞就会流失,DRAM 所储存的资料也就会流失。

    要解决这个问题,最简单的就是使用介电常数够大(K 值够大)的材料来取代“氧化矽”为绝缘体,让电子与电洞可以储存在电容里不会流失。目前业界使用“钛锆酸铅”(PZT)或“钽铋酸锶”(SBT)这种介电常数很大(K 值很大)的“铁电材料”(Ferroelectric material)来取代氧化矽,则可以储存电子与电洞不会流失,让原本“易失性”的动态随机存取存储器(DRAM)变成“非易失性”的存储器称为“铁电随机存取存储器”(Ferroelectric RAM,FRAM)。

    NAND Flash 又是什么呢?

    继续讲讲非易失性的部分:

    Flash(闪存)由于具备了重量轻、体积小、功率低等优点,被应用在各类电子产品的硬盘上。Flash 又可以分成 NOR 型 Flash 和 NAND 型 Flash。

    NOR Flash 比 NAND Flash 更早导入市场。读取的速度较快,但写入的速度慢、价格也比 NAND Flash 贵。

    目前用来储存操作系统的程序代码或重要数据,比如拿来做 ROM。像是生产 NOR Flash 的台厂旺宏就是因为打入任天堂 Switch 主机的 ROM 供应链,今年营收上看攀升。

    NAND Flash 写入的速度快、价格较低,故目前以 NAND Flash 最为普遍。现在的 USB 硬盘和手机储存空间,就是用 NAND Flash 为主流技术。

    另外,固态硬盘(Solid State Drive, SSD)也是以 NAND 型 Flash 为基础所建构的储存装置。SSD 不像传统硬盘(HDD)中有马达、读写臂等零件。速度慢、功耗高,对震动又相当敏感,很难用在小型行动装置中。

    SSD 在读写数据时不会有噪音,耐震、传输速度快、重量又能缩减到 HDD 十分之一以上,现在已经成为个人计算机和笔记本电脑的主流储存设备。

                                    

              支付宝打赏                                              微信打赏  

    总结:

    依照停止供应电源的话、是否还能保留数据,分成”易失性”与”非易失性”存储。

    易失性存储分成 DRAM 和 SRAM。

    SRAM 更快但价格更贵,所以主存储器多用 DRAM、快取多用 SRAM。

    非易失性存储分成 ROM 和 Flash。主要用来作为硬盘。

    Flash 又分成 NOR Flash 与 NAND Flash,现在硬盘多以 NAND Flash 构成的 SSD 为主。

    展开全文
  • Flash硬件知识

    千次阅读 2018-02-02 17:00:30
    Flash全名叫做Flash Memory,从名字就能看出,是种数据存储设备,存储设备有很多类,Flash属于非易失性存储设备(Non-volatile Memory Device),与此相对应的是易失性存储设备(Volatile Memory Device)。关于什么是非...
    1.2.1. 什么是Flash
    

    Flash全名叫做Flash Memory,从名字就能看出,是种数据存储设备,存储设备有很多类,Flash属于非易失性存储设备(Non-volatile Memory Device),与此相对应的是易失性存储设备(Volatile Memory Device)。关于什么是非易失性/易失性,从名字中就可以看出,非易失性就是不容易丢失,数据存储在这类设备中,即使断电了,也不会丢失,这类设备,除了Flash,还有其他比较常见的入硬盘,ROM等,与此相对的,易失性就是断电了,数据就丢失了,比如大家常用的内存,不论是以前的SDRAM,DDR SDRAM,还是现在的DDR2,DDR3等,都是断电后,数据就没了。

    1.2.1.1. Flash的硬件实现机制

    Flash的内部存储是MOSFET,里面有个悬浮门(Floating Gate),是真正存储数据的单元。

    在Flash之前,紫外线可擦除(uv-erasable)的EPROM,就已经采用了Floating Gate存储数据这一技术了。

    图 1.1. 典型的Flash内存单元的物理结构

    典型的Flash内存单元的物理结构


    数据在Flash内存单元中是以电荷(electrical charge) 形式存储的。存储电荷的多少,取决于图中的外部门(external gate)所被施加的电压,其控制了是向存储单元中冲入电荷还是使其释放电荷。而数据的表示,以所存储的电荷的电压是否超过一个特定的阈值Vth来表示,因此,Flash的存储单元的默认值,不是0(其他常见的存储设备,比如硬盘灯,默认值为0),而是1,而如果将电荷释放掉,电压降低到一定程度,表述数字0。



    1.2.2. 什么是Nand Flash

    Flash主要分两种,Nand Flash和nor flash。

    关于Nand Flash和Nor Flash的区别,参见[6]

    不过,关于两者区别,除了那个解释之外,这里再多解释解释:

    1. Nor的成本相对高,容量相对小,比如常见的只有128KB,256KB,1MB,2MB等等,优点是读写数据时候,不容易出错。所以在应用领域方面,Nor Flash比较适合应用于存储少量的代码。

    2. Nand flash成本相对低,说白了就是便宜,缺点是使用中数据读写容易出错,所以一般都需要有对应的软件或者硬件的数据校验算法,统称为ECC。但优点是,相对来说容量比较大,现在常见的Nand Flash都是1GB,2GB,更大的8GB的都有了,相对来说,价格便宜,因此适合用来存储大量的数据。其在嵌入式系统中的作用,相当于PC上的硬盘,用于存储大量数据。

    所以,一个常见的应用组合就是,用小容量的Nor Flash存储启动代码,比如uboot,用大容量的Nand Flash做整个系统和用户数据的存储。

    而一般的嵌入式平台的启动流程也就是,系统从装有启动代码的Nor Flash启动后,初始化对应的硬件,包括SDRAM等,然后将Nand Flash上的Linux 内核读取到内存中,做好该做的事情后,就跳转到SDRAM中去执行内核了,然后内核解压(如果是压缩内核的话,否则就直接运行了)后,开始运行,在Linux内核启动最后,去Nand Flash上,挂载根文件,比如jffs2,yaffs2等,挂载完成,运行初始化脚本,启动consle交互,才允许你通过console和内核交互。至此完成整个系统启动过程。

    而Nor Flash就分别存放的是Uboot,Nand Flash存放的是Linux的内核镜像和根文件系统,以及余下的空间分成一个数据区。

    1.2.2.1. Nand Flash的详细分类

    Nand Flash,按照硬件类型,可以分为

    1. Bare NAND chips

      裸片。单独的Nand Flash芯片。

    2. SmartMediaCards

      裸片+一层薄塑料。常用于数码相机和MP3播放器中。之所以称smart,是由于其软件smart,而不是硬件本身有啥smart之处。

    3. DiskOnChip

      裸片+glue logic。glue logic=硬件ECC产生器+用于静态的nand 芯片控制的寄存器+直接访问一小片地址窗口,那块地址中包含了引导代码的stub桩,其可以从Nand Flash中拷贝真正的引导代码。

    1.2.3. SLC和MLC的实现机制

    Nand Flash按照内部存储数据单元的电压的不同层次,也就是单个内存单元中,是存储1位数据,还是多位数据,可以分为SLC和MLC。

    1.2.3.1. SLC(Single Level Cell)

    单个存储单元,只存储一位数据,表示1或0。

    就是上面介绍的,对于数据的表示,单个存储单元中内部所存储电荷的电压,和某个特定的阈值电压Vth,相比,如果大于此Vth值,就是表示1,反之,小于Vth,就表示0。

    对于Nand Flash的数据的写入1,就是控制External Gate去充电,使得存储的电荷够多,超过阈值Vth,就表示1了。而对于写入0,就是将其放电,电荷减少到小于Vth,就表示0了。

    关于为何Nand Flash不能从0变成1,我的理解是,物理上来说,是可以实现每一位的,从0变成1的,但是实际上,对于实际的物理实现,出于效率的考虑,如果对于,每一个存储单元都能单独控制,即,0变成1就是,对每一个存储单元单独去充电,所需要的硬件实现就很复杂和昂贵,同时,所进行对块擦除的操作,也就无法实现之前所说的的,Flash的速度,即一闪而过的速度了,也就失去了Flash的众多特性了。

    1.2.3.2. MLC(Multi Level Cell)

    与SLC相对应的,就是单个存储单元,可以存储多个位,比如2位,4位等。其实现机制,说起来比较简单,就是通过控制内部电荷的多少,分成多个阈值,通过控制里面的电荷多少,而达到我们所需要的存储成不同的数据。比如,假设输入电压是Vin=4V(实际没有这样的电压,此处只是为了举例方便),那么,可以设计出2的2次方=4个阈值, 1/4 的Vin=1V,2/4的Vin=2V,3/4的Vin=3V,Vin=4V,分别表示2位数据00,01,10,11,对于写入数据,就是充电,通过控制内部的电荷的多少,对应表示不同的数据。

    对于读取,则是通过对应的内部的电流(与Vth成反比),然后通过一系列解码电路完成读取,解析出所存储的数据。这些具体的物理实现,都是有足够精确的设备和技术,才能实现精确的数据写入和读出的。

    单个存储单元可以存储2位数据的,称作2的2次方=4 Level Cell,而不是2 Level Cell,关于这点,之前看Nand flash的数据手册(datasheet)的时候,差点搞晕了。

    同理,对于新出的单个存储单元可以存储4位数据的,称作 2的4次方=16 Level Cell。

    1.2.3.3. 关于如何识别SLC还是MLC

    Nand Flash设计中,有个命令叫做Read ID,读取ID,意思是读取芯片的ID,就像大家的身份证一样,这里读取的ID中,是读取好几个字节,一般最少是4个,新的芯片,支持5个甚至更多,从这些字节中,可以解析出很多相关的信息,比如此Nand Flash内部是几个芯片(chip)所组成的,每个chip包含了几片(Plane),每一片中的页大小,块大小,等等。在这些信息中,其中有一个,就是识别此flash是SLC还是MLC。下面这个就是最常见的Nand Flash的datasheet中所规定的,第3个字节,3rd byte,所表示的信息,其中就有SLC/MLC的识别信息:

    表 1.1. Nand Flash第3个ID的含义

    Description I/O7 I/O6 I/O5 I/O4 I/O3 I/O2 I/O1 I/O0
    Internal Chip Number

    1
    2
    4
    8

    0 0
    0 1
    1 0
    1 1

    Cell Type

    2 Level Cell
    4 Level Cell
    4 Level Cell
    8 Level Cell

    0 0
    0 1
    1 0
    1 1

    Number of Simultaneously Programmed Pages

    1
    2
    4
    8

    0 0
    0 1
    1 0
    1 1

    Interleave Program Between multiple chips

    Not Support
    Support

    0
    1

    Cache Program

    Not Support
    Support

    0
    1




    1.2.4. Nand Flash数据存储单元的整体架构

    简单说就是,常见的Nand Flash,内部只有一个chip,每个chip只有一个plane。

    而有些复杂的,容量更大的Nand Flash,内部有多个chip,每个chip有多个plane。这类的Nand Flash,往往也有更加高级的功能,比如下面要介绍的Multi Plane Program和Interleave Page Program等。

    概念上,由大到小来说,就是:

    Nand Flash ⇒ Chip ⇒ Plane ⇒ Block ⇒ Page ⇒ oob

    用图表来表示,更加易懂:

    图 1.2. Nand Flash的结构图

    Nand Flash的结构图


    比如,型号为K9K8G08U0A这块Nand Flash(有时候也被称为此块chip芯片),其内部有两个K9F4G08U0A的chip,chip#1和chip#2,每个K9F4G08U0A的chip包含了2个Plane,每个Plane是2Gbbit,所以K9F4G08U0A的大小是2Gb×2 = 4Gb = 512MB,因此,K9K8G08U0A内部有2个K9F4G08U0A,或者说4个Plane,总大小是×256MB=1GB。

    用公式表示如下:

    公式 1.1. K9K8G08U0A的物理结构所组成的总容量

    K9K8G08U0A(这块Nand Flash)

    = 2 × K9F4G08U0A(K9F4G08U0A是chip,1 K9F4G08U0A = 2 Plane)

    = 2 × 2个Plane

    = 4 Plane(1 Plane = 2048 Block)

    = 4 × 2048个Block(1 Block = 64 Page)

    = 4 × 2048 × 64Page(1 Page = 2KB)

    = 4 × 2048 × 64Page × 2KB

    = 4 × 2048 × 128KB(1 Block = 128KB)

    = 4 × 256MB(1 Plane = 2Gb = 256MB)

    = 2 × 512MB(1 K9F4G08U0A = 4Gb = 512MB)

    = 1GB(1 K9K8G08U0A = 1GB)


    而型号是K9WAG08U1A的Nand Flash,内部包含了2个K9K8G08U0A,所以,总容量是K9K8G08U0A的两倍=1GB×2=2GB,类似地K9NBG08U5A,内部包含了4个K9K8G08U0A,总大小就是4×1GB=4GB。

    [注意] 通常只关心Nand的总大小

    上面所说的block,page等Nand Flash的物理上的组织结构,是在chip的基础上来说的,但是软件编程的时候,除非你要用到Multi Plane Program和Interleave Page Program等,一般很少区分内部有几个chip以及每个chip有几个plane,而最关心的只是Nand Flash的总体容量size有多大,比如是1GB还是2GB等等。

    下面详细介绍一下,Nand Flash的一个chip内部的硬件逻辑组织结构。

    1.2.5. Nand Flash的物理存储单元的阵列组织结构

    Nand Flash的内部组织结构,此处还是用图来解释,比较容易理解:

    图 1.3. Nand Flash物理存储单元的阵列组织结构

    Nand Flash物理存储单元的阵列组织结构


    上图是K9K8G08U0A的datasheet中的描述。

    简单解释就是:

    1.2.5.1. Block块

    一个Nand Flash(的chip,芯片)由很多个块(Block)组成,块的大小一般是128KB,256KB,512KB,此处是128KB。其他的小于128KB的,比如64KB,一般都是下面将要介绍到的small block的Nand Flash。

    块Block,是Nand Flash的擦除操作的基本/最小单位。

    1.2.5.2. Page页

    每个块里面又包含了很多页(page)。每个页的大小,对于现在常见的Nand Flash多数是2KB,最新的Nand Flash的是4KB、8KB等,这类的页大小大于2KB的Nand Flash,被称作big block的Nand Flash,对应的发读写命令地址,一共5个周期(cycle),而老的Nand Flash,页大小是256B,512B,这类的Nand Flash被称作small block,地址周期只有4个。

    页Page,是读写操作的基本单位。

    不过,也有例外的是,有些Nand Flash支持subpage(1/2页或1/4页)子页的读写操作,不过一般很少见。

    1.2.5.3. oob / Redundant Area / Spare Area

    每一个页,对应还有一块区域,叫做空闲区域(spare area)/冗余区域(redundant area),而Linux系统中,一般叫做OOB(Out Of Band),这个区域,是最初基于Nand Flash的硬件特性:数据在读写时候相对容易错误,所以为了保证数据的正确性,必须要有对应的检测和纠错机制,此机制被叫做EDC(Error Detection Code)/ECC(Error Code Correction, 或者 Error Checking and Correcting),所以设计了多余的区域,用于放置数据的校验值。

    Oob的读写操作,一般是随着页的操作一起完成的,即读写页的时候,对应地就读写了oob。

    关于oob具体用途,总结起来有:

    1. 标记是否是坏快
    2. 存储ECC数据
    3. 存储一些和文件系统相关的数据。如jffs2就会用到这些空间存储一些特定信息,而yaffs2文件系统,会在oob中,存放很多和自己文件系统相关的信息。

    1.2.6. Flash名称的由来

    Flash的擦除操作是以block块为单位的,与此相对应的是其他很多存储设备,是以bit位为最小读取/写入的单位,Flash是一次性地擦除整个块:在发送一个擦除命令后,一次性地将一个block,常见的块的大小是128KB/256KB。。,全部擦除为1,也就是里面的内容全部都是0xFF了,由于是一下子就擦除了,相对来说,擦除用的时间很短,可以用一闪而过来形容,所以,叫做Flash Memory。所以一般将Flash翻译为 (快速)闪存。

    1.2.7. Flash相对于普通设备的特殊性

    根据上面提到过的,Flash最小操作单位,相对于普通存储设备,就显得有些特殊。

    因为一般存储设备,比如硬盘或内存,读取和写入都是以位(bit)为单位,读取一个bit的值,将某个值写入对应的地址的位,都是可以按位操作的。

    但是Flash由于物理特性,使得内部存储的数据,只能从1变成0,这点,这点可以从前面的内部实现机制了解到,对于最初始值,都是1,所以是0xFFFFFFFF,而数据的写入,即是将对应的变成0,而将数据的擦出掉,就是统一地,以block为单位,全部一起充电,所有位,都变成初始的1,而不是像普通存储设备那样,每一个位去擦除为0。而数据的写入,就是电荷放电的过程,代表的数据也从1变为了0。

    所以,总结一下Flash的特殊性如下:

    表 1.2. Flash和普通设备相比所具有的特殊性

    普通设备(硬盘/内存等) Flash
    读取/写入的叫法 读取/写入 读取/编程(Program)
    读取/写入的最小单位 Bit/位 Page/页
    擦除(Erase)操作的最小单位 Bit/位 Block/块
    擦除操作的含义 将数据删除/全部写入0 将整个块都擦除成全是1,也就是里面的数据都是0xFF
    对于写操作 直接写即可 在写数据之前,要先擦除,然后再写








    [提示] 提示


    1. 之所以将写操作叫做编程,是因为flash是从之前的EPROM、EEPROM等继承发展而来,而之前的EEPROM,往里面写入数据,就叫做编程Program,之所以这么称呼,是因为其对数据的写入,是需要用电去擦除/写入的,所以叫做编程。

    2. 对于目前常见的页大小是2K/4K的Nand Flash,其块的大小有128KB/256KB/512KB等。而对于Nor Flash,常见的块大小有64K/32K等。

    3. 在写数据之前,要先擦除,内部就都变成0xFF了,然后才能写入数据,也就是将对应的位由1变成0。

    1.2.8. Nand Flash的位反转特性

    Nand Flash的位反转,也叫做位翻转,对应的英文表达有:Bit Flip=Bit Flipping=Bit-Flip=Bit twiddling。

    Nand Flash由于本身硬件的内在特性,会导致(极其)偶尔的出现位反转的现象。

    所谓的位反转,bit flip,指的是原先Nand Flash中的某个位,变化了,即要么从1变成0了,要么从0变成1了。

    1.2.8.1. Nand Flash位反转的原因

    Nand Flash的位反转现象,主要是由以下一些原因/效应所导致:

    1. 漂移效应(Drifting Effects)

      漂移效应指的是,Nand Flash中cell的电压值,慢慢地变了,变的和原始值不一样了。

    2. 编程干扰所产生的错误(Program-Disturb Errors)

      此现象有时候也叫做,过度编程效应(over-program effect)。

      对于某个页面的编程操作,即写操作,引起非相关的其他的页面的某个位跳变了。

    3. 读操作干扰产生的错误(Read-Disturb Errors)

      此效应是,对一个页进行数据读取操作,却使得对应的某个位的数据,产生了永久性的变化,即Nand Flash上的该位的值变了。

    1.2.8.2. Nand Flash位反转的影响

    位反转,说白了,就是读取数据的时候,数据出错了。

    因此,如果你读取的数据正好是属于某个重要的文件中的数据,比如系统的配置文件等,那么此时错了一位,都会导致系统出现异常,问题相对会很严重。

    而如果此数据属于音视频流中的数据,那么此时即使错了一位,对整个音视频的播放产生的影响也很小,所以问题也不大。

    1.2.8.3. Nand Flash位反转的类型和解决办法

    对应的位反转的类型,有两种:

    1. 一种是nand flash物理上的数据存储的单元上的数据,是正确的,只是在读取此数据出来的数据中的某位,发生变化,出现了位反转,即读取出来的数据中,某位错了,本来是0变成1,或者本来是1变成0了。此处可以成为软件上位反转。此数据位的错误,当然可以通过一定的校验算法检测并纠正。
    2. 另外一种,就是nand flash中的物理存储单元中,对应的某个位,物理上发生了变化,原来是1的,变成了0,或原来是0的,变成了1,发生了物理上的位的数据变化。此处可以成为硬件上的位反转。此错误,由于是物理上发生的,虽然读取出来的数据的错误,可以通过软件或硬件去检测并纠正过来,但是物理上真正发生的位的变化,则没办法改变了。不过个人理解,好像也是可以通过擦除Erase整个数据块Block的方式去擦除此错误,不过在之后的Nand Flash的使用过程中,估计此位还是很可能继续发生同样的硬件的位反转的错误。

    以上两种类型的位反转,其实对于从Nand Flash读取出来的数据来说,解决其中的错误的位的方法,都是一样的,即通过一定的校验算法,常称为ECC,去检测出来,或检测并纠正错误。

    如果只是单独检测错误,那么如果发现数据有误,那么再重新读取一次即可。

    实际中更多的做法是,ECC校验发现有错误,会有对应的算法去找出哪位错误并且纠正过来。

    其中对错误的检测和纠正,具体的实现方式,有软件算法,也有硬件实现,即硬件Nand Flash的控制器controller本身包含对应的硬件模块以实现数据的校验和纠错的。

    1.2.9. Nand Flash引脚(Pin)的说明

    图 1.4. Nand Flash引脚功能说明

    Nand Flash引脚功能说明


    上图是常见的Nand Flash所拥有的引脚(Pin)所对应的功能,简单翻译如下:

    表 1.3. Nand Flash引脚功能的中文说明

    引脚名称 引脚功能
    I/O0 ~ I/O7 用于输入地址/数据/命令,输出数据
    CLE Command Latch Enable,命令锁存使能,在输入命令之前,要先在模式寄存器中,设置CLE使能
    ALE Address Latch Enable,地址锁存使能,在输入地址之前,要先在模式寄存器中,设置ALE使能
    CE# Chip Enable,芯片使能,在操作Nand Flash之前,要先选中此芯片,才能操作
    RE# Read Enable,读使能,在读取数据之前,要先使CE#有效。
    WE# Write Enable,写使能, 在写取数据之前,要先使WE#有效
    WP# Write Protect,写保护
    R/B# Ready/Busy Output,就绪/忙,主要用于在发送完编程/擦除命令后,检测这些操作是否完成,忙,表示编程/擦除操作仍在进行中,就绪表示操作完成
    Vcc Power,电源
    Vss Ground,接地
    N.C Non-Connection,未定义,未连接








    [提示] 数据手册中的#表示低电平

    在数据手册中,你常会看到,对于一个引脚定义,有些字母上面带一横杠的,那是说明此引脚/信号是低电平有效,比如你上面看到的RE头上有个横线,就是说明,此RE是低电平有效,此外,为了书写方便,在字母后面加“#”,也是表示低电平有效,比如我上面写的CE#;如果字母头上啥都没有,就是默认的高电平有效,比如上面的CLE,就是高电平有效。

    1.2.9.1. 为何需要ALE和CLE

    硬件上,有了电源的Vcc和接地的Vss等引脚,很好理解,但是为何还要有ALE和CLE这样的引脚,为何设计这么多的命令,把整个系统搞这么复杂,关于这点,最后终于想明白了:

    设计命令锁存使能(Command Latch Enable, CLE) 和 地址锁存使能(Address Latch Enable,ALE),那是因为,Nand Flash就8个I/O,而且是复用的,也就是,可以传数据,也可以传地址,也可以传命令,为了区分你当前传入的到底是啥,所以,先要用发一个CLE(或ALE)命令,告诉Nand Flash的控制器一声,我下面要传的是命令(或地址),这样,里面才能根据传入的内容,进行对应的动作。否则,Nand Flash内部,怎么知道你传入的是数据,还是地址,还是命令,也就无法实现正确的操作了。

    1.2.9.2. Nand Flash只有8个I/O引脚的好处

    在Nand Flash的硬件设计中,你会发现很多个引脚。关于硬件上为何设计这样的引脚,而不是直接像其他存储设备,比如普通的RAM,直接是一对数据线引出来,多么方便和好理解啊。

    关于这样设计的好处:

    1.2.9.2.1. 减少外围连线

    相对于并口(Parellel)的Nor Flash的48或52个引脚来说,的确是大大减小了引脚数目,这样封装后的芯片体积,就小很多。现在芯片在向体积更小,功能更强,功耗更低发展,减小芯片体积,就是很大的优势。同时,减少芯片接口,也意味着使用此芯片的相关的外围电路会更简化,避免了繁琐的硬件连线。

    1.2.9.2.2. 提高系统的可扩展性

    因为没有像其他设备一样用物理大小对应的完全数目的addr引脚,在芯片内部换了芯片的大小等的改动,对于用全部的地址addr的引脚,那么就会引起这些引脚数目的增加,比如容量扩大一倍,地址空间/寻址空间扩大一倍,所以,地址线数目/addr引脚数目,就要多加一个,而对于统一用8个I/O的引脚的Nand Flash,由于对外提供的都是统一的8个引脚,内部的芯片大小的变化或者其他的变化,对于外部使用者(比如编写Nand Flash驱动的人)来说,不需要关心,只是保证新的芯片,还是遵循同样的接口,同样的时序,同样的命令,就可以了。这样就提高了系统的扩展性。

    说白了,对于旧的Nand Flash所实现的驱动,这些软件工作,在换新的硬件的Nand Flash的情况下,仍然可以工作,或者是通过极少的修改,就同样可以工作,使得软硬件兼容性大大提高。


    1.2.10. Nand Flash的一些典型(typical)的特性

    1. 页擦除时间是200us,有些慢的有800us
    2. 块擦除时间是1.5ms
    3. 页数据读取到数据寄存器的时间一般是20us
    4. 串行访问(Serial access)读取一个数据的时间是25ns,而一些旧的Nand Flash是30ns,甚至是50ns
    5. 输入输出端口是地址和数据以及命令一起multiplex复用的
    6. Nand Flash的编程/擦除的寿命:即,最多允许的擦除的次数

      以前老的Nand Flash,编程/擦除时间比较短,比如K9G8G08U0M,才5K次,而后来的多数也只有10K=1万次,而现在很多新的Nand Flash,技术提高了,比如,Micron的MT29F1GxxABB,Numonyx的 NAND04G-B2D/NAND08G-BxC,都可以达到100K,也就是10万次的编程/擦除,达到和接近于之前常见的Nor Flash,几乎是同样的使用寿命了。

    7. 封装形式

      48引脚的TSOP1封装 或 52引脚的ULGA封装

    1.2.11. Nand Flash控制器与Nand Flash芯片

    关于Nand Flash的控制器Controller和Nand Flash芯片chip之间的关系,觉得有必要解释一下:

    首先,我们要知道的是,我们写驱动,是写Nand Flash 控制器的驱动,而不是Nand Flash 芯片的驱动,因为独立的Nand Flash芯片,一般来说,是很少直接拿来用的,多数都是硬件上有对应的硬件的Nand Flash的控制器,去操作和控制Nand Flash,包括提供时钟信号,提供硬件ECC校验等等功能,我们所写的驱动软件,是去操作Nand Flash的控制器

    然后由控制器去操作Nand Flash芯片,实现我们所要的功能。

    1.2.12. Nand Flash中的特殊硬件结构

    由于Nand Flash相对其他常见设备来说,比较特殊,所以,特殊的设备,就有特殊的设计,就对应某些特殊的硬件特性,就有必要解释解释:

    页寄存器(Page Register)

    由于Nand Flash读取和编程操作来说,一般最小单位是页,所以Nand Flash在硬件设计时候,就考虑到这一特性,对于每一片(Plane),都有一个对应的区域专门用于存放,将要写入到物理存储单元中去的或者刚从存储单元中读取出来的,一页的数据,这个数据缓存区,本质上就是一个缓存buffer,但是只是此处datasheet里面把其叫做页寄存器page register而已,实际将其理解为页缓存,更贴切原意。

    而正是因为有些人不了解此内部结构,才容易产生之前遇到的某人的误解,以为内存里面的数据,通过Nand Flash的FIFO,写入到Nand Flash里面去,就以为立刻实现了实际数据写入到物理存储单元中了,而实际上只是写到了这个页缓存中,只有当你再发送了对应的编程第二阶段的确认命令,即0x10,之后,实际的编程动作才开始,才开始把页缓存中的数据,一点点写到物理存储单元中去。

    所以,简单总结一下就是,对于数据的流向,实际是经过了如下步骤:

    图 1.5. Nand Flash读写时的数据流向

    Nand Flash读写时的数据流向


    1.2.13. Nand Flash中的坏块(Bad Block)

    Nand Flash中,一个块中含有1个或多个位是坏的,就称为其为坏块Bad Block。

    坏块的稳定性是无法保证的,也就是说,不能保证你写入的数据是对的,或者写入对了,读出来也不一定对的。与此对应的正常的块,肯定是写入读出都是正常的。

    1.2.13.1. 坏块的分类

    坏块有两种:

    1. 出厂时就有存在的坏块

      一种是出厂的时候,也就是,你买到的新的,还没用过的Nand Flash,就可以包含了坏块。此类出厂时就有的坏块,被称作factory (masked) bad block或initial bad/invalid block,在出厂之前,就会做对应的标记,标为坏块。

    2. 使用过程中产生的坏块

      第二类叫做在使用过程中产生的,由于使用过程时间长了,在擦块除的时候,出错了,说明此块坏了,也要在程序运行过程中,发现,并且标记成坏块的。具体标记的位置,和上面一样。这类块叫做worn-out bad block。即用坏了的块。

    1.2.13.2. 坏块的标记

    具体标记的地方是,对于现在常见的页大小为2K的Nand Flash,是块中第一个页的oob起始位置(关于什么是页和oob,下面会有详细解释)的第1个字节(旧的小页面,pagesize是512B甚至256B的Nand Flash,坏块标记是第6个字节),如果不是0xFF,就说明是坏块。相对应的是,所有正常的块,好的块,里面所有数据都是0xFF的。

    不过,对于现在新出的有些Nand Flash,很多标记方式,有些变化,有的变成该坏块的第一个页或者第二个页,也有的是,倒数最后一个或倒数第二个页,用于标记坏块的。

    具体的信息,请参考对应的Nand Flash的数据手册,其中会有说明。

    对于坏块的标记,本质上,也只是对应的flash上的某些字节的数据是非0xFF而已,所以,只要是数据,就是可以读取和写入的。也就意味着,可以写入其他值,也就把这个坏块标记信息破坏了。对于出厂时的坏块,一般是不建议将标记好的信息擦除掉的。

    uboot中有个命令是

    nand scrub

    就可以将块中所有的内容都擦除了,包括坏块标记,不论是出厂时的,还是后来使用过程中出现而新标记的。一般来说,不建议用这个。

    不过,在实际的驱动编程开发过程中,为了方便起见,我倒是经常用,其实也没啥大碍,呵呵。不过呢,其实最好的做法是,用

    nand erase

    只擦除好的块,对于已经标记坏块的块,不要轻易擦除掉,否则就很难区分哪些是出厂时就坏的,哪些是后来使用过程中用坏的了。

    1.2.13.3. 坏块的管理

    对于坏块的管理,在Linux系统中,叫做坏块管理(BBM,Bad Block Management),对应的会有一个表去记录好块,坏块的信息,以及坏块是出厂就有的,还是后来使用产生的,这个表叫做坏块表(BBT,Bad Block Table)。在Linux 内核MTD架构下的Nand Flash驱动,和Uboot中Nand Flash驱动中,在加载完驱动之后,如果你没有加入参数主动要求跳过坏块扫描的话,那么都会去主动扫描坏块,建立必要的BBT的,以备后面坏块管理所使用。

    1.2.13.4. 坏块的比例

    而关于好块和坏块,Nand Flash在出厂的时候,会做出保证:

    1. 关于好的,可以使用的块的数目达到一定的数目,比如三星的K9G8G08U0M,整个flash一共有4096个块,出厂的时候,保证好的块至少大于3996个,也就是意思是,你新买到这个型号的Nand Flash,最坏的可能, 有3096-3996=100个坏块。不过,事实上,现在出厂时的坏块,比较少,绝大多数,都是使用时间长了,在使用过程中出现的。
    2. 保证第一个块是好的,并且一般相对来说比较耐用。做此保证的主要原因是,很多Nand Flash坏块管理方法中,就是将第一个块,用来存储上面提到的BBT,否则,都是出错几率一样的块,那么也就不太好管理了,连放BBT的地方,都不好找了,^_^。

    一般来说,不同型号的Nand Flash的数据手册中,也会提到,自己的这个Nand Flash,最多允许多少个坏块。就比如上面提到的,三星的K9G8G08U0M,最多有100个坏块。

    1.2.14. Nand Flash中页的访问顺序

    在一个块内,对每一个页进行编程的话,必须是顺序的,而不能是随机的。比如,一个块中有128个页,那么你只能先对page0编程,再对page1编程,。。。。,而不能随机的,比如先对page3,再page1,page2,page0,page4,。。。

    关于此处对于只能顺序给页编程的说法,只是翻译自datasheet,但是实际情况却发现是,程序中没有按照此逻辑处理,可以任意对某Block内的Page去做Program的动作,而不必是顺序的。但是datasheet为何如此解释,原因未知,有待知情者给解释一下。

    1.2.15. 常见的Nand Flash的操作

    要实现对Nand Flash的操作,比如读取一页的数据,写入一页的数据等,都要发送对应的命令,而且要符合硬件的规定,如图:

    图 1.6. Nand Flash K9K8G08U0A的命令集合

    Nand Flash K9K8G08U0A的命令集合


    从上图可以看到,如果要实现读一个页的数据,就要发送Read的命令,而且是分两个周期(Cycle),即分两次发送对应的命令,第一次是0x00h,第二次是0x30h,而两次命令中间,需要发送对应的你所要读取的页的地址,关于此部分详细内容,留待后表。

    对应地,其他常见的一些操作,比如写一个页的数据(Page Program),就是先发送0x80h,然后发生要写入的地址,再发送0x10h。

    [注意] 注意

    对于不同厂家的不同型号的Nand Flash 的基本操作,即读页数据Read Page,写页数据(对页进行编程)Page Program,擦除整个块的数据Erase Block等操作,所用的命令都是一样的,但是针对一些Nand Flash的高级的一些特性,比如交错页编程(Interleave Page Program),多片同时编程(Simultaneously Program Multi Plane)等,所用的命令,未必一样,不过对于同一厂家的Nand Flash的芯片,那一般来说,都是统一的。

    关于一些常见的操作,比如读一个页的Read操作和写一个页的Page Program,下面开始更深入的介绍。

    1.2.15.1. 页编程(Page Program)注意事项

    Nand flash的写操作叫做编程Program,编程,一般情况下,是以页为单位的。

    有的Nand Flash,比如K9K8G08U0A,支持部分页编程(Partial Page Program),但是有一些限制:在同一个页内的,连续的部分页的编程,不能超过4次。

    一般情况下,都是以页为单位进行编程操作的,很少使用到部分页编程。

    关于这个部分页编程,本来是一个页的写操作,却用两个命令或更多的命令去实现,看起来是操作多余,效率不高,但是实际上,有其特殊考虑:

    至少对于块擦除来说,开始的命令0x60是擦除设置命令(erase setup comman),然后传入要擦除的块地址,然后再传入擦除确认命令(erase confirm command)0xD0,以开始擦除的操作。

    这种完成单个操作要分两步发送命令的设计,即先开始设置,再最后确认的命令方式,是为了避免由于外部由于无意的/未预料而产生的噪音,比如,由于某种噪音,而产生了0x60命令,此时,即使被Nand Flash误认为是擦除操作,但是没有之后的确认操作0xD0,Nand Flash就不会去擦除数据,这样使得数据更安全,不会由于噪音而误操作。

    1.2.15.2. 读(Read)操作过程详解

    下面以最简单的read操作为例,解释如何理解时序图,以及将时序图中的要求,转化为代码。

    解释时序图之前,让我们先要搞清楚,我们要做的事情:

    从Nand Flash的某个页Page里面,读取我们要的数据。

    要实现此功能,会涉及到几部分的知识,即使我们不太懂Nand Flash的细节,但是通过前面的基本知识的介绍,那么以我们的常识,至少很容易想到的就是,需要用到哪些命令,怎么发这些命令,怎么计算所需要的地址,怎么读取我们要的数据等等。

    下面就一步步的解释,需要做什么,以及如何去做:

    1.2.15.2.1. 需要使用何种命令

    首先,是要了解,对于读取数据,要用什么命令:

    根据前面关于Nand Flash的命令集合介绍,我们知道,要读取数据,要用到Read命令,该命令需要2个周期,第一个周期发0x00,第二个周期发0x30。

    1.2.15.2.2. 发送命令前的准备工作以及时序图各个信号的具体含义

    知道了用何命令后,再去了解如何发送这些命令。

    [提示] 提示

    在开始解释前,关于”使能”这个词要罗嗦一下,以防有些读者和我以前一样,在听这类词语的时候,属于初次接触,或者接触不多的,就很容易被搞得一头雾水的(虽然该词汇对于某些专业人士说是属于最基本的词汇了,囧)。

    使能(Enable),是指使其(某个信号)有效,使其生效的意思,“使其”“能够”怎么怎么样。。。。比如,上面图中的CLE线号,是高电平有效,如果此时将其设为高电平,我们就叫做,将CLE使能,也就是使其生效的意思。

    图 1.7. Nand Flash数据读取操作的时序图

    Nand Flash数据读取操作的时序图








    [注意] 注意
    此图来自三星的型号K9K8G08U0A的Nand Flash的数据手册(datasheet)。

    我们来一起看看,我在图6中的特意标注的①边上的黄色竖线。

    黄色竖线所处的时刻,是在发送读操作的第一个周期的命令0x00之前的那一刻。

    让我们看看,在那一刻,其所穿过好几行都对应什么值,以及进一步理解,为何要那个值。

    1. 黄色竖线穿过的第一行,是CLE。还记得前面介绍命令所存使能(CLE)那个引脚吧?CLE,将CLE置1,就说明你将要通过I/O复用端口发送进入Nand Flash的,是命令,而不是地址或者其他类型的数据。只有这样将CLE置1,使其有效,才能去通知了内部硬件逻辑,你接下来将收到的是命令,内部硬件逻辑,才会将受到的命令,放到命令寄存器中,才能实现后面正确的操作,否则,不去将CLE置1使其有效,硬件会无所适从,不知道你传入的到底是数据还是命令了。
    2. 而第二行,是CE#,那一刻的值是0。这个道理很简单,你既然要向Nand Flash发命令,那么先要选中它,所以,要保证CE#为低电平,使其有效,也就是片选有效。
    3. 第三行是WE#,意思是写使能。因为接下来是往Nand Flash里面写命令,所以,要使得WE#有效,所以设为低电平。
    4. 第四行,是ALE是低电平,而ALE是高电平有效,此时意思就是使其无效。而对应地,前面介绍的,使CLE有效,因为将要数据的是命令(此时是发送图示所示的读命令第二周期的0x30),而不是地址。如果在其他某些场合,比如接下来的要输入地址的时候,就要使其有效,而使CLE无效了。
    5. 第五行,RE#,此时是高电平,无效。可以看到,知道后面低6阶段,才变成低电平,才有效,因为那时候,要发生读取命令,去读取数据。
    6. 第六行,就是我们重点要介绍的,复用的输入输出I/O端口了,此刻,还没有输入数据,接下来,在不同的阶段,会输入或输出不同的数据/地址。
    7. 第七行,R/B#,高电平,表示R(Ready)/就绪,因为到了后面的第5阶段,硬件内部,在第四阶段,接受了外界的读取命令后,把该页的数据一点点送到页寄存器中,这段时间,属于系统在忙着干活,属于忙的阶段,所以,R/B#才变成低,表示Busy忙的状态的。

    介绍了时刻①的各个信号的值,以及为何是这个值之后,相信,后面的各个时刻,对应的不同信号的各个值,大家就会自己慢慢分析了,也就容易理解具体的操作顺序和原理了。

    1.2.15.2.3. 如何计算出我们要传入的行地址和列地址

    在介绍具体读取数据的详细流程之前,还要做一件事,那就是,先要搞懂我们要访问的地址,以及这些地址,如何分解后,一点点传入进去,使得硬件能识别才行。

    此处还是以K9K8G08U0A为例,此Nand Flash,一共有8192个块,每个块内有64页,每个页是2K+64 Bytes。

    假设,我们要访问其中的第7000个块中的第64页中的1208字节处的地址,此时,我们就要先把具体的地址算出来:

    物理地址

    =块大小×块号 + 页大小×页号 + 页内地址

    =128K×7000 + 2K×64 + 1208

    =0x36B204B8

    接下来,我们就看看,怎么才能把这个实际的物理地址,转化为Nand Flash所要求的格式。

    在解释地址组成之前,先要来看看其datasheet中关于地址周期的介绍:

    图 1.8. Nand Flash的地址周期组成

    Nand Flash的地址周期组成


    结合图 1.7 “Nand Flash数据读取操作的时序图”中的2,3阶段,我们可以看出,此Nand Flash地址周期共有5个,2个列(Column)周期,3个行(Row)周期。

    1. 对应地,列地址A0~A10,就是页内地址,地址范围是从0到2047。

      细心的读者可能注意到了,为何此处多出来个A11呢?

      这样从A0到A11,一共就是12位,可以表示的范围就是0~212,即0~4096了。

      实际上,由于我们访问页内地址,可能会访问到oob的位置,即2048-2111这64个字节的范围内,所以,此处实际上只用到了2048~2111,用于表示页内的oob区域,其大小是64字节。

    2. 对应地,A12~A30,称作页号,页的号码,可以定位到具体是哪一个页。

      A18~A30,表示对应的块号,即属于哪个块。

    简单解释完了地址组成,那么就很容易分析上面例子中的地址了。

    注意,下面这样的方法,是错误的:

    0x36B204B8 = 11 0110 1011 0010 0000 0100 1011 1000,分别分配到5个地址周期就是:

    1st周期 A7 ~ A0 1011 1000 = 0xB8
    2nd周期 A11~ A8 0100 = 0x04
    3rd周期 A19~A12 0010 0000 = 0x20
    4th周期 A27~A20 0110 1011 = 0x6B
    5th周期 A30~A28 11 = 0x03
    [注意] 注意
    图 1.7 “Nand Flash数据读取操作的时序图”中对应的,*L,意思是地电平,由于未用到那些位,datasheet中强制要求设为0,所以,才有上面的2nd周期中的高4位是0000.其他的A30之后的位也是类似原理,都是0。

    而至于上述计算方法为何是错误的,那是因为上面计算过程中,把第11位的值,本来是属于页号的位A11,给算成页内地址里面的值了。

    应该是这样计算,才是对的:

    0x36B204B8 = 11 0110 1011 0010 0000 0100 1011 1000

    1st周期 A7 ~ A0 1011 1000 = 0xB8
    2nd周期 A10~ A8 100 = 0x04
    3rd周期 A19~A12 010 0000 0 = 0x40
    4th周期 A27~A20 110 1011 0 = 0xD6
    5th周期 A30~A28 11 0 = 0x06

    那有人会问了,上面表11中,不是明明写的A0到A30,其中包括A11,不是正好对应着此处地址中的bit0到bit30吗?

    其实,我开始也是犯了同样的错误,误以为我们要传入的地址的每一位,就是对应着表11中的A0到A30呢,实际上,表11中的A11,是比较特殊的,只有当我们访问页内地址处于oob的位置,即属于2048~2111的时候,A11才会其效果,才会用A0-A11用来表示对应的某个属于2048~2111的某个值,属于oob的某个位置。

    而我们此处的页内地址为2108,还没有超过2047呢,所以A11肯定是0。

    这么解释,显得很绕,很难看懂。

    换种方式来解释,就容易听懂了:

    说白了,我们就是要访问第7000个块中的第64页中的1208字节处,对应着

    页内地址

    =1208

    =0x4B8

    页号

    =块数×页数/块 + 块内的页号

    = 7000×(128K/2K) + 64

    = 7000×64 + 64

    = 448064

    =0x6D640

    也就是,我们要访问0x6D640页内的0x4B8地址,这样很好理解吧,^_^。

    然后对应的:

    页内地址=0x4B8

    分成两个对应的列地址,就变成

    0x4B8 :列地址1=0xB8,列地址2=0x04

    页号=0x6D640,分成三个行号就是:

    0x6D640:行号1=0x40,行号2=0xD6,行号3=0x06

    再回头看看上面的计算方法,

    最开始计算出来的:

    列地址1=0xB8

    列地址2=0x04

    行号1=0x20

    行号2=0x6B

    行号3=0x03

    是错误的。

    而第二次计算正确的:

    列地址1=0xB8

    列地址2=0x04

    行号1=0x40

    行号2=0xD6

    行号3=0x06

    才是对的,也和我们此处自己手动计算,是一致的。

    第一次之所以计算错,就是错误的把行地址的最低一位A11,放到列地址中的最高位了。

    至此,才算把如何手动计算行地址和列地址,解释明白和正确了。

    对应的,Linux的源码\drivers\mtd\nand\nand_base.c中,也是这样处理的:

    static void nand_command_lp(struct mtd_info *mtd, unsigned int command,
                    int column, int page_addr)
    {
    ......
            /* Serially input address */
            if (column != -1) {
    ......
                chip->cmd_ctrl(mtd, column, ctrl); /* 发送Col Addr 1 */
                ctrl &= ~NAND_CTRL_CHANGE;
                chip->cmd_ctrl(mtd, column >> 8, ctrl); /* 发送Col Addr 2 */
            }
            if (page_addr != -1) {
                chip->cmd_ctrl(mtd, page_addr, ctrl); /* 发送Row Addr 1 */
                chip->cmd_ctrl(mtd, page_addr >> 8, /* 发送Row Addr 2 */
                           NAND_NCE | NAND_ALE); 
                /* One more address cycle for devices > 128MiB */
                if (chip->chipsize > (128 << 20))
                    chip->cmd_ctrl(mtd, page_addr >> 16, /* 发送Row Addr 3 */
                               NAND_NCE | NAND_ALE); 
            }
    }
                    

    1

    column,即页内地址,多数情况下,都是0,即使不是0,也可以直接通过将传入的地址除于页地址所得的余数,就是列地址了。

    2

    page_addr即页号,也很简单,就是通过要访问的地址,除于页大小,即可得到。

    因此,我们要访问第7000个块中的第64页中的1208字节处的话,所要传入的地址就是分5个周期:

    分别传入两个列地址的:

    列地址1=0xB8

    列地址2=0x04

    然后再传3个行地址的:

    行号1=0x40

    行号2=0xD6

    行号3=0x06

    这样硬件才能识别。

    而接下来的内容,也就是介绍硬件是如何处理这些输入的。

    1.2.15.2.4. 读操作过程的解释

    准备工作终于完了,下面就可以开始解释说明,对于读操作的,上面图中标出来的,1-6个阶段,具体是什么含义。

    操作准备阶段:此处是读(Read)操作,所以,先发一个图5中读命令的第一个阶段的0x00,表示,让硬件先准备一下,接下来的操作是读。

    发送两个周期的列地址。也就是页内地址,表示,我要从一个页的什么位置开始读取数据。

    接下来再传入三个行地址。对应的也就是页号。

    然后再发一个读操作的第二个周期的命令0x30。接下来,就是硬件内部自己的事情了。

    Nand Flash内部硬件逻辑,负责去按照你的要求,根据传入的地址,找到哪个块中的哪个页,然后把整个这一页的数据,都一点点搬运到页缓存中去。而在此期间,你所能做的事,也就只需要去读取状态寄存器,看看对应的位的值,也就是R/B#那一位,是1还是0,0的话,就表示,系统是busy,仍在”忙“(着读取数据),如果是1,就说系统活干完了,忙清了,已经把整个页的数据都搬运到页缓存里去了,你可以接下来读取你要的数据了。

    对于这里。估计有人会问了,这一个页一共2048+64字节,如果我传入的页内地址,就像上面给的1208一类的值,只是想读取1028到2011这部分数据,而不是页开始的0地址整个页的数据,那么内部硬件却读取整个页的数据出来,岂不是很浪费吗?答案是,的确很浪费,效率看起来不高,但是实际就是这么做的,而且本身读取整个页的数据,相对时间并不长,而且读出来之后,内部数据指针会定位到你刚才所制定的1208的那个位置。

    接下来,就是你“窃取“系统忙了半天之后的劳动成果的时候了,呵呵。通过先去Nand Flash的控制器中的数据寄存器中写入你要读取多少个字节(byte)/字(word),然后就可以去Nand Flash的控制器的FIFO中,一点点读取你要的数据了。

    至此,整个Nand Flash的读操作就完成了。

    对于其他操作,可以根据我上面的分析,一点点自己去看datasheet,根据里面的时序图去分析具体的操作过程,然后对照代码,会更加清楚具体是如何实现的。


    1.2.16. Nand Flash的一些高级特性

    1.2.16.1. Nand Flash的Unique ID

    1.2.16.1.1. 什么是Unique ID唯一性标识

    Unique ID,翻译为中文就是,独一无二的ID,唯一性标识。

    很明显,这个Unique ID是为了用来识别某些东西的,每一个东西都拥有一个独一无二的标识信息。

    在Nand Flash里面的Unique ID,主要是某个ID信息,保证每个Nand Flash都是独一无二的。主要用于其它的使用Nand Flash的用户,根据此unique id去做加密等应用,实现某些安全方面的应用。

    简而言之,就是用Nand Flash的Unique ID来实现安全相关的应用,比如加密,版权保护等等。

    1.2.16.1.2. 不同Nand Flash厂商的对Unique ID的不同的实现方法

    此处,继续解释之前,还要再次赘述一下:

    目前Nand Flash的厂家有samsung,Toshiba,Intel, Hynix,Micron,Numonyx,Phison ,SanDisk,Sony,Spansion等。

    由于前面所说的Nand Flash的规范之争,即Toshiba & Samsung和Intel + 其它厂商(Hynix,Micron,Numonyx,Phison ,SanDisk,Sony,Spansion等)之争,导致对于Unique ID这么个小的功能点(feature),不同的方面,弄出了不同的实现(做法)。

    下面就来解释一下各个厂家关于Unique ID的实现方法,以及如何读取对应的Unique ID:

    1.2.16.1.2.1. Toshiba东芝的Nand的Unique ID

    网上找到一个datasheet:

    Toshiba TH58NS512DC

    http://datasheet.elcodis.com/pdf/11/23/112371/th58ns512dc-to51y.pdf

    中有提到:

    P1:The TH58NS512DC is a SmartMediaTM with ID and each device has 128 bit unique ID number embedded in the device. This unique ID number is applicable to image files, music files, electronic books, and so on where copyright protection is required.即每个Toshiba的TH58NS512DC中,都有一个128 bit=16 byte的Unique ID,可用于图片,音乐,电子书等应用中的版权保护。

    P24:

    图 1.9. Toshiba的Unique ID

    Toshiba的Unique ID


    1.2.16.1.2.2. 读取Toshiba的Nand的Unique ID

    从上面可以看出,Toshiba的Nand中,关于Unique ID,是需要先通过普通的0x90,即Read ID的命令,去读取Nand的ID,找到第三个字节(3rd byte),然后判断其是否是0xA5,如果是0xA5,然后才能确定此Nand里面是有Unique ID的,然后才有去读取Unique ID这一说。

    而关于如何独缺Unique ID,则需要和自己去联系Toshiba,和其签订NDA协议后,才可得知读取Nand的Unique ID的方法。

    1.2.16.1.3. Samsung三星的Nand的Unique ID

    网上找到的某款三星的Nand的datasheet:

    Samsung K9F5608U0B

    http://hitmen.c02.at/files/docs/psp/ds_k9f5608u0b_rev13.pdf

    6. Unique ID for Copyright Protection is available

    - The device includes one block sized OTP (One Time Programmable), which can be used to increase system security or to provide identification capabilities. Detailed information can be obtained by contact with Samsung

    即,Samsung的Nand的Unique ID,也和Toshiba的用途类似,也主要是用于版权保护,但是其实现却不同。

    Samsung的Unique ID的实现,是专门在Nand 里面配备了一个OTP的Block,而此Nand芯片的Block大小是16KB。

    而关于如何操作此OTP,即如何写入数据和读取数据,此处未说明。

    欲知详情,请联系三星。

    不过个人理解,应该和普通的block的操作类似,即普通的block,包含很多page,每个page的操作,有对应的page read,用对应的page read命令来读取此特殊的OTP的block里面的数据。

    而此OTP的block里面的数据是什么,完全取决于自己最开始往里面写入了什么数据。说白了就是,你根据自己需求,在你的产品出厂的时候,写入对应的数据,比如该款产品的SN序列号等数据,然后自己在用page read读取出相应数据后,自己解析,得到自己要的信息,用于自己的用途,比如版权保护等。

    1.2.16.1.3.1. 读取Samsung的Nand的Unique ID

    如前所述:

    关于如何操作此OTP的block,即如何写入数据和读取数据,此处未说明。

    即想要知道如何读取Samsung的Nand的Unique ID,请自己去问三星。

    1.2.16.1.4. 遵循ONFI规范的厂商的Nand的Unique ID

    主要指的是Intel英特尔,Hynix海力士,Micron美光,Numonyx恒亿,Spansion飞索等公司。

    对应的Nand 的Unique ID的相关定义,ONFI的规范中都有,现简要摘录如下:

    ONFI 2.2

    http://onfi.org/wp-content/uploads/2009/02/ONFI%202_2%20Gold.pdf

    ONFI规范中,在5.7.1. Parameter Page Data Structure Definition中,如图:

    图 1.10. ONFI的参数页数据结构定义

    ONFI的参数页数据结构定义


    定义了一个page的数据,用于存储对应的Nand的各种参数,其中,有一个第8个字节的bit5==1的时候,表示支持“Read Unique ID”的命令,即说明此Nand芯片支持此命令,如果byte8的bit5==0,那么说明不支持,也就没法去读Unique ID了。

    1.2.16.1.4.1. 读取遵循ONFI的厂商的Nand的Unique ID

    如果经过上述判断,此符合ONFI的Nand Flash支持Read Unique ID命令,次此时就可以通过该命令来读取对应的Nand Flash的Unique ID了。

    此Read Unique ID的详细解释为:

    5.8. Read Unique ID Definition

    The Read Unique ID function is used to retrieve the 16 byte unique ID (UID) for the device. The unique ID when combined with the device manufacturer shall be unique.

    The UID data may be stored within the Flash array. To allow the host to determine if the UID is without bit errors, the UID is returned with its complement, as shown in Table 47. If the XOR of the UID and its bit-wise complement is all ones, then the UID is valid.

    即用Read Unique ID命令来读取128bit=16字节的Unique ID,但是呢,为了用于防止写入的Unique ID有误,因此在16字节后面又添了个对应的补码,即每位取反的结果,这样前16字节的Unique ID和后16字节的Unique ID的补码,构成了32字节,算作一组,如下图所示:

    图 1.11. ONFI中Unique ID的结构

    ONFI中Unique ID的结构



    To accommodate robust retrieval of the UID in the case of bit errors, sixteen copies of the UID and the corresponding complement shall be stored by the target. For example, reading bytes 32-63 returns to the host another copy of the UID and its complement. Read Status Enhanced shall not be used during execution of the Read Unique ID command


    Figure 57 defines the Read Unique ID behavior. The host may use any timing mode supported by the target in order to retrieve the UID data.


    而为了进一步防止出错,将上面32字节算一组,重复了16次,将这16个32字节的数据,存在Nand Flash里面,然后用Read Unique ID命令去读取出来,取得其中某个32字节即可,然后判断前16字节和后16字节取反,如果结果所有位都是1,那么结果即为16个0xFF,那么说明Unique ID是正确的。否则说明有误。

    Read Unique ID的命令的详细格式如下图所示:

    图 1.12. ONFI中Read Unique ID命令的时序图

    ONFI中Read Unique ID命令的时序图


    即先发送0xED命令,再发送0x00地址,然后等待Nand Flash的busy状态结束,就可以读取出来的那16组的32个字节了,然后用上面办法去判断,找到前16字节和后16字节异或得到结果全部16字节都为0xFF,即说明得到了正确的Unique ID。至此,符合ONFI规范的Unique ID,就读取出来了。




    1.2.16.2. 片选无关(CE don’t-care)技术

    很多Nand flash支持一个叫做CE don’t-care的技术,字面意思就是,不关心是否片选。

    对此也许有人会问了,如果不片选,那还能对其操作吗?答案就是,这个技术,主要用在当时是不需要选中芯片,但是芯片内部却仍可以继续操作的这些情况:在某些应用,比如录音,音频播放等应用中,外部使用的微秒(us)级的时钟周期,此处假设是比较少的2us,在进行读取一页或者对页编程时,是对Nand Flash操作,这样的串行(Serial Access)访问的周期都是20/30/50ns,都是纳秒(ns)级的,此处假设是50ns,当你已经发了对应的读或写的命令之后,接下来只是需要Nand Flash内部去自己操作,将数据读取除了或写入进去到内部的数据寄存器中而已,此处,如果可以把片选取消,CE#是低电平有效,取消片选就是拉高电平,这样会在下一个外部命令发送过来之前,即微秒量级的时间里面,即2us-50ns≈2us,这段时间的取消片选,可以降低很少的系统功耗,但是多次的操作,就可以在很大程度上降低整体的功耗了。

    总的来说就是:由于某些外部应用所需要的访问Nand Flash的频率比较低,而Nand Flash内部操作速度比较快,所以在针对Nand Flash的读或写操作的大部分时间里面,都是在等待外部命令的输入,同时却选中芯片,产生了多余的功耗,此“不关心片选”技术,就是在Nand Flash的内部的相对快速的操作(读或写)完成之后,就取消片选,以节省系统功耗。待下次外部命令/数据/地址输入来的时候,再选中芯片,即可正常继续操作了。这样,整体上,就可以大大降低系统功耗了。

    [提示] 提示
    1. 如果想要操作硬件Nand Flash芯片,先要将对应的CE#(低有效)片选信号拉低,选中该芯片,然后才能做接下来的读写操作所要做的发命令,发数据等动作。
    2. Nand Flash的片选与否,功耗差别会有很大。如果数据没有记错的话,我之前遇到我们系统里面的Nand Flash的片选,大概有5个mA的电流输出呢,也许你对5mA没太多概念,给你说个数据你就知道了:当时为了针对MP3播放功耗进行优化,整个系统优化之后的待机功耗,也才10个mA左右的,所以节省5mA已经算是很不错的功耗优化了。

    1.2.16.3. 带EDC的拷回操作以及Sector的定义(Copy-Back Operation with EDC & Sector Definition for EDC)

    Copy-Back功能,简单的说就是,将一个页的数据,拷贝到另一个页。

    如果没有Copy-Back功能,那么正常的做法就是,先要将那个页的数据拷贝出来放到内存的数据buffer中,读出来之后,再用写命令将这页的数据,写到新的页里面。

    而Copy-Back功能的好处在于,不需要用到外部的存储空间,不需要读出来放到外部的buffer里面,而是可以直接读取数据到内部的页寄存器(page register)然后写到新的页里面去。

    而且,为了保证数据的正确,要硬件支持EDC(Error Detection Code)的,否则,在数据的拷贝过程中,可能会出现错误,并且拷贝次数多了,可能会累积更多错误。

    而对于错误检测来说,硬件一般支持的是512字节数据,对应有16字节用来存放校验产生的ECC数值,而这512字节一般叫做一个扇区。对于2K+64字节大小的页来说,按照512字节分,分别叫做A,B,C,D区,而后面的64字节的oob区域,按照16字节一个区,分别叫做E,F,G,H区,对应存放A,B,C,D数据区的ECC的值。

    Copy-Back编程的主要作用在于,去掉了数据串行读取出来,再串行写入进去的时间,所以,而这部分操作,是比较耗时的,所以此技术可以提高编程效率,提高系统整体性能。

    1.2.16.4. 多片同时编程(Simultaneously Program Multi Plane)

    对于有些新出的Nand Flash,支持同时对多个片进行编程,比如上面提到的三星的K9K8G08U0A,内部包含4片(Plane),分别叫做Plane0,Plane1,Plane2,Plane3。.由于硬件上,对于每一个Plane,都有对应的大小是2048+64=2112字节的页寄存器(Page Register),使得同时支持多个Plane编程成为可能。 K9K8G08U0A支持同时对2个Plane进行编程。

    不过要注意的是,只能对Plane0和Plane1或者Plane2和Plane3,同时编程,而不支持Plane0和Plane2同时编程。

    1.2.16.5. 交错页编程(Interleave Page Program)

    多片同时编程,是针对一个chip里面的多个Plane来说的,

    而此处的交错页编程,是指对多个chip而言的。

    可以先对一个chip,假设叫chip1,里面的一页进行编程,然后此时,chip1内部就开始将数据一点点写到页里面,就出于忙的状态了,而此时可以利用这个时间,对出于就绪状态的chip2,也进行页编程,发送对应的命令后,chip2内部也就开始慢慢的写数据到存储单元里面去了,也出于忙的状态了。此时,再去检查chip1,如果编程完成了,就可以开始下一页的编程了,然后发完命令后,就让其内部慢慢的编程吧,再去检查chip2,如果也是编程完了,也就可以进行接下来的其他页的编程了。如此,交互操作chip1和chip2,就可以有效地利用时间,使得整体编程效率提高近2倍,大大提高Nand Flash的编程/擦写速度了。

    1.2.16.6. 随机输出页内数据(Random Data Output In a Page)

    在介绍此特性之前,先要说说,与Random Data Output In a Page相对应的是,普通的,正常的,sequential data output in a page。

    正常情况下,我们读取数据,都是先发读命令,然后等待数据从存储单元到内部的页数据寄存器中后,我们通过不断地将RE#(Read Enale,低电平有效)置低,然后从我们开始传入的列的起始地址,一点点读出我们要的数据,直到页的末尾,当然有可能还没到页地址的末尾,就不再读了。所谓的顺序(sequential)读取也就是,根据你之前发送的列地址的起始地址开始,每读一个字节的数据出来,内部的数据指针就加1,移到下个字节的地址,然后你再读下一个字节数据,就可以读出来你要的数据了,直到读取全部的数据出来为止。

    而此处的随机(random)读取,就是在你正常的顺序读取的过程中,先发一个随机读取的开始命令0x05命令,再传入你要将内部那个数据指针定位到具体什么地址,也就是2个cycle的列地址,然后再发随机读取结束命令0xE0,然后,内部那个数据地址指针,就会移动到你所制定的位置了,你接下来再读取的数据,就是从那个制定地址开始的数据了。

    而Nand Flash数据手册里面也说了,这样的随机读取,你可以多次操作,没限制的。

    请注意,上面你所传入的地址,都是列地址,也就是页内地址,也就是说,对于页大小为2K的Nand Flash来说,所传入的地址,应该是小于2048+64=2112的。

    不过,实际在Nand Flash的使用中,好像这种用法很少的。绝大多数,都是顺序读取数据。



    展开全文
  • flash_all.bat

    2017-08-17 23:49:27
    flash_all.bat
  • flash详解

    千次阅读 2019-06-04 16:52:26
    Flash全名叫做Flash Memory,从名字就能看出,是种数据存储设备,存储设备有很多类,Flash属于非易失性存储设备(Non-volatile Memory Device),与此相对应的是易失性存储设备(Volatile Memory Device)。关于什么是非...
  • 本文将详细介绍STM32的Flash读写相关的函数及这些函数在我们的程序中的应用
  • flash

    2019-12-31 15:43:04
    Flash必须死!
  • STM32的内部Flash读写

    千次阅读 2018-05-17 20:35:23
    内部Flash官方手册介绍 STM官方关于stm32F103系列的芯片有一个专门介绍flash读写的手册。STM32F100xx超值型产品闪存编程手册 通过J-Link查看自己的芯片内部的flash大小。我的大小为512k 是high-density 手册中对应...
  • flash 区别

    千次阅读 2014-12-18 22:47:42
    NAND类型Flash通常称之为串行Flash,其操作指令、操作地址和操作数据是通过同一 个8位总线传输,具有较少的硬件连接。NOR类型Flash通常称之为并行Flash,其地址总 线和数据总线是分开的,存取速度较快,但硬件连接较...
  • 当你将高版本卸载后,再安装低版本时,会出现这种情况,提示你“正尝试安装的 Adobe (R) Flash (R) Player 版本不是最新的版本。请访问 http://www.adobe.com/go/getflashplayer 以获取最新、最安全的版本。”。...
  • Flash 安装、修复和诊断工具分享(Flash大厅)1 ~~各种~~安装1.1 下载1.2 Flash 安装2 修复 每次使用Flash Player,总是会碰到各种各样的麻烦问题。作者今天在开网页中的FlashFlash又坏了,但是同时机缘巧合的发现...
  • NAND flash和NOR flash的区别详解

    万次阅读 多人点赞 2017-12-24 13:03:22
    我们使用的智能手机除了有一个可用的空间(如苹果8G、16G等),还有一个RAM容量,很多人都不是很清楚,为什么需要二个这样的芯片做存储呢,这就是我们下面要讲到的。这二种存储设备我们都统称为“FLASH”,FLASH是一...
  • 解决Flash弹窗广告

    万次阅读 多人点赞 2019-02-06 09:00:27
    本人下了一个flash(可能不是从官网)安装后日常弹出一个图标为&amp;amp;quot;FF&amp;amp;quot;的广告,困扰已久,痛定思痛打算搞定它。 当弹窗广告再次出现时,打开任务管理器,看到了一个叫&amp;amp;...
  • Flash Helper Service 这个流氓,动不动弹出广告!!

    万次阅读 多人点赞 2019-06-28 22:48:49
    C:\Windows\SysWOW64\Macromed\Flash\FlashHelperService.exe,是重庆的一家公司,重庆重橙网络科技有限公司,真够流氓的。动不动弹出不堪入目的乱七八糟的广告。 哪何,如何清除呢? 1.按快捷键CTRL+Shift+ESC就...