搞内核存储驱动的人,这几年应该都有个同感:MTD层的变化,尤其是NAND ECC这块,动静不小。最近重读了Miquel Raynal在ELCE 2020上的那个分享,再对照最新的内核代码,发现当时埋下的伏笔,现在基本都兑现了。我们就顺着内核维护者的思路,复盘一下这套ECC框架是怎么从一团乱麻走到今天的。一、历史包袱:为什么以前的ECC代码那么难改?
那时候,一个NAND驱动通常把这几件事揉在一起:操作NAND颗粒、控制NAND控制器、运行ECC算法。这在只有“片上ECC”(On-die ECC)或者简单的硬件ECC时代没问题。但随着SoC越来越复杂,出现了独立于NAND控制器之外的专用ECC引擎(Pipeline ECC),老框架就傻眼了——它没法描述“NAND控制器A使用外部ECC引擎B”这种拓扑关系。Miquel当时接手维护工作时,面对的就是这么一个局面:代码能用,但没法扩展。想加个新硬件,就得在旧的板级代码里打各种补丁。二、核心思路:把ECC引擎当成“一等公民”
重构的核心思想其实很朴素:把ECC引擎从NAND控制器里剥离出来,让它成为一个独立的、可被发现的实体。这就好比以前你是公司保洁兼行政,现在要把保洁部门独立出来,别的部门按需采购服务。为了实现这一点,内核引入了几个关键的数据结构,这套东西现在已经是NAND驱动的基石了:struct nand_ecc_engine(服务提供者)这就是那个“独立的保洁公司”。不管ECC算法跑在CPU软算、片上硬件还是外挂芯片,在内核眼里,它们都是nand_ecc_engine。它们被注册到一个全局链表里,等待被“聘用”。struct nand_ecc_props(服务属性)用来描述ECC的能力:是用Hamming还是BCH?步长(step size)是多少?纠错强度(strength)多大?这就像公司的资质证书。struct nand_ecc(服务协商与消费)这是连接NAND芯片和ECC引擎的纽带。这里有个很有意思的逻辑:优先级仲裁。三、算法选型:Hamming、BCH与RS的现实差距
理论课上大家都学过各种纠错码,但在Linux内核里落地,讲究的是性价比。Hamming(汉明码):老伙计了。1950年发明的东西,现在还在服役。主要给老旧的SLC用。它的优点是硬件实现极简单,缺点也很明显:只能纠1比特。现在除了维护旧驱动,基本不会新选它。BCH:目前的绝对主力。为什么说它适合NAND?因为它不挑人。你可以灵活配置步长和纠错能力,而且它对计算资源的消耗相对线性。现在的NAND控制器,只要带硬件ECC,99%都是BCH。内核里的软件BCH实现也是为了兜底。Reed-Solomon(RS码):这玩意儿是按“符号”纠错的,天生适合对付突发错误(Burst Errors)。光盘(CD/DVD)用它很合适,但在NAND世界里,除非你的存储介质特别烂,否则一般不用它,因为计算复杂度比BCH高。四、设备树:从“配置”到“描述”
配合内核代码的重构,设备树(Device Tree)的写法也变了。以前是告诉内核“用什么模式”,现在是告诉内核“硬件之间怎么连”。举个SPI-NAND的例子,以前可能就是一个属性ecc-mode = "hw"。现在呢?flash@0 {nand-ecc-engine = <&flash>; /* 指向自己 */};
flash@0 {nand-ecc-engine = <&ecc_controller>; /* 指向外部引擎 */};
这种写法把硬件的物理连接关系描述得非常精准。这也是为什么现在SPI-NAND能支持外置ECC了,而并行NAND因为历史包袱太重,重构进度慢了一截。五、现状与未来:还得继续填坑
虽然这套框架在v5.x之后已经稳定,但Miquel当时提到的几个痛点,至今还在。首先是并行NAND(Parallel NAND)。那里的代码太老了,很多驱动还在用旧的API。要把它们全部迁移到新的ECC引擎框架,工作量巨大,而且容易引发回归(Regression)。内核社区对这种改动非常谨慎。其次是Bootloader。U-Boot里的MTD栈通常滞后于Linux内核。现在Linux里玩得很溜的外置ECC,在U-Boot里往往还没跟上,这会导致双系统启动时的兼容性问题。最后提一句汽车电子。现在ASIL-B/D认证开始要求NOR Flash也带ECC。好消息是,我们现在这套框架完全可以复用,不用再推倒重来。说不定哪天,我们就要开始给NOR写ECC支持了。