尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ARM Linux下CF卡驱动:绕过SD栈,走ATA/PCMCIA正确路线

ARM Linux下CF卡驱动:绕过SD栈,走ATA/PCMCIA正确路线 简介在嵌入式Linux开发中驱动栈的选择往往决定外设能否被正确识别。CF卡与传统SD卡虽然外观相似但底层协议差异巨大SD卡走MMC子系统而CF卡在ARM平台上通常走ATA/PCMCIA驱动栈。理解CF卡的三类工作模式Memory、I/O、True IDE以及内核中libata与pcmcia子系统的分界线是正确配置设备树和内核选项的前提。通过合理设置CONFIG_ATA、PATA驱动和平台设备资源将CF槽注册为磁盘设备即可在/dev下看到sda节点。实践中常见片选错配、中断风暴、时序不稳等问题可借助dmesg、hdparm与吞吐测试快速定位。本文从驱动栈原理出发给出ARM Linux下CF卡从内核配置、设备树编写到读写挂载的完整路径帮助开发者避开“把CF当SD”的典型误区。1. 在 ARM Linux 板上让 CF 卡跑起来先要分清 CF 和 SD 是两条驱动栈拿到一块带 CF 槽的 ARM 板子插上卡开机内核日志里什么都没有dmesg 扫不到 /dev/sda 也扫不到 /dev/mmcblk0。很多人第一反应是去搜“linux CF sd 驱动”然后找一堆与 SD 卡驱动相关的手册来试结果浪费一下午。这里先设立一个最核心的认知CF 卡和 SD 卡在 Linux 下面的驱动栈完全不一样CF 卡在 ARM 平台上走的是 ATA/PCMCIA 这条老线路而 SD 卡走的是 mmc 子系统的线路。标题里同时出现 CF 和 sd恰恰是要你别把它们混为一谈。这篇文章面向的是要在 ARM 板、嵌入式设备上把 CF 卡识别成一块可用磁盘、并能读写挂载的人也覆盖了从内核配置到调试排错的全过程。新手可以按步骤把卡跑起来老手能从此文的踩坑记录里节省半天定位时间。2. CF 卡驱动在 Linux 里的两条路ATA/PCMCIA 栈和 SD 栈的分界线在哪2.1 先分清 CF 卡的三张脸Memory 模式、I/O 模式和 True IDE 模式CF 卡规范里定义了三种工作模式Memory 模式、I/O 模式和 True IDE 模式。很多 ARM 板上的 CF 槽在硬件上只接了一部分信号线并不支持全部三种模式。比如老款 PXA 平台的开发板大多把 CF 槽接在系统总线上靠 PCMCIA 控制器做 Host而 AT91 平台更常见的是把 CF 槽接成 True IDE 模式直接映射到外部总线。Memory 模式下CF 卡就像一块普通的存储器不弹 ATA 命令驱动得按寄存器方式去操作I/O 模式多用于 PC Card 设备的 IO 访问True IDE 模式下 CF 卡化身为一块硬盘主机的 IDE/ATA 控制器可以直接操作它。绝大多数嵌入式应用目标是把 CF 卡当硬盘因此用 True IDE 模式最省事。判断板子上 CF 槽处于哪种模式一个笨办法是看 CF 座的 39 号引脚UDID电平拉高就是 True IDE 模式拉低则非 IDE 模式。这个引脚在调试时极其关键后文避坑部分会再次提到。2.2 内核里三套驱动栈ATA 栈、PCMCIA 栈和最容易认错的 SD/MMC 栈Linux 内核里处理 CF 卡相关设备时真正会碰到的子系统分三套。ATA 栈由 libata 组成CF 卡被识别为 /dev/sda顶层用 sd_mod 驱动PCMCIA 栈提供 pcmcia 核心和 pcmcia 驱动CF 卡作为存储设备时最终也会挂到 ide/pata 驱动下第三套是 SD/MMC 栈mmc core mmc_block这是 SD 卡走的路径设备名是 /dev/mmcblk0。驱动栈对应内核选项CF 卡识别设备名常见 ARM 控制器ATA (libata)CONFIG_ATA、CONFIG_PATA_AT91 等/dev/sdaAT91、PXAPCMCIACONFIG_PCMCIA、CONFIG_AT91_CF 等/dev/hda 或 /dev/sdaPXA、S3C24xxSD/MMCCONFIG_MMC、CONFIG_MMC_SDHCI/dev/mmcblk0i.MX、Allwinner 等把 CF 卡插到一个 USB/SD 读卡器里再接到 ARM 板那走的是读卡器对应的驱动路径跟原生 CF 槽完全是两码事。真正在嵌入式 ARM 板上找“CF 驱动”去 drivers/ata 和 drivers/pcmcia 目录里找而不是在 drivers/mmc 里找。2.3 三分钟定位你的板卡走哪条驱动栈我一般会按下面三步来判断一块 ARM 板上的 CF 槽该用哪套驱动。第一步看芯片板上 CF 槽旁边有没有独立的 PCMCIA/CF 控制器芯片还是说 SoC 直接引出的总线第二步看内核日志厂商 BSP 启动时 dmesg 里会出现 cf、pcmcia、ata 之类的关键字如果什么都没有说明驱动根本没注册第三步看 menuconfig在 Device Drivers 菜单里找有没有与 SoC 型号同名的 PATA/PCMCIA 项。把这三步过一遍基本就能确定工作方向。注意别一开始就去改设备树——大部分 CF 驱动问题不是设备树字段配错而是连该挂哪个子系统都没想清楚。一旦方向错了后面所有调试都是白费力气。3. 从内核配置到设备树把 CF 槽注册进 ARM Linux 的最小步骤3.1 交叉编译工具链与内核配置的最小命令集拿到一块 ARM 板要跑 CF 卡第一步是准备交叉编译环境。常见做法是安装官方工具链比如 arm-linux-gnueabihf-gcc 用于 32 位 ARMaarch64-linux-gnu-gcc 用于 64 位这部分对做 arm 交叉编译的工程师来说是基本功但有几个细节值得注意。我一般会把架构和交叉编译前缀写进环境变量避免每次 make 都带一遍参数。export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf- make 厂商默认config # 例如 imx_v7_defconfig、at91_dt_defconfig make menuconfig配置内核时的关键项如下路径在 Device Drivers 下注意 ATA 和 PCMCIA 两处别漏# 如果 CF 走 ATA 栈 CONFIG_ATAy CONFIG_PATA_AT91y # AT91 平台 CONFIG_PATA_PXAy # PXA 平台 # 如果 CF 走 PCMCIA 栈 CONFIG_PCMCIAy CONFIG_AT91_CFy # 强烈建议打开调试输出 CONFIG_PCMCIA_DEBUGymenuconfig 里打开这些项后直接编译内核。编译时优先用 zImage 和 dtbs 目标除非你确定厂商 BSP 要求 uImage。这里有个 linux 常用命令的常见坑make -j$(nproc) 在低配虚拟机里经常 OOM建议根据内存大小手动限制并行任务数比如 make -j4。3.2 用平台设备和设备树把 CF 槽注册进内核有些 ARM 平台没有设备树支持CF 控制器驱动通过平台设备注册。下面这段是经典的 board 文件写法早期 AT91 平台的 CF 驱动就是这么挂上去的。核心是把 IO 资源、中断资源填到 platform_device 里驱动通过 name 字段匹配。static struct resource cf_res[] { { .start CF_BASE_ADDR, /* CF 槽映射的总线地址 */ .end CF_BASE_ADDR SZ_4K - 1, .flags IORESOURCE_MEM, }, { .start gpio_to_irq(CF_IRQ_GPIO), /* GPIO 中断转 IRQ */ .end gpio_to_irq(CF_IRQ_GPIO), .flags IORESOURCE_IRQ, }, }; static struct platform_device cf_device { .name at91_cf, .id -1, .num_resources ARRAY_SIZE(cf_res), .resource cf_res, }; static int __init board_cf_init(void) { return platform_device_register(cf_device); }这段代码有三个参数值得解释。CF_BASE_ADDR 来自板子原理图是 CF 槽片选对应的片选地址通常由 SoC 外部总线控制器决定配错会直接访问非法地址。gpio_to_irq 把 CF 卡的中断引脚从 GPIO 编号转换为中断控制器认识的编号如果板子 CF 中断没有接 GPIO 而是直连 SoC 中断脚这里要改成对应的 IRQ 号。最后 platform_device_register 要求驱动已经在内核里否则注册了也没人认领。现代 ARM 平台普遍用设备树设备树里描述 CF 控制器的节点框架如下具体 compatible 与 reg 的个数要对照你内核里对应驱动的 probe 函数来填ebi { cf_slot: compact-flash0 { compatible atmel,at91rm9200-cf; reg 0x0 0x0 0x0; atmel,memory-region cf_mem; atmel,irq-pins 1; pinctrl-names default; pinctrl-0 pinctrl_cf; status okay; }; };设备树里最容易出错的是 reg 的三段含义第一个是片选号第二个是片选内偏移第三个是地址空间大小。比如 reg 0x3 0x0 0x8000 表示挂在第三个片选上起始地址偏移为 0大小 32KB。atmel,memory-region 指向你在 /reserved-memory 里定义的 DMA 内存区域不是所有平台都需要pinctrl-0 必须覆盖 CF 的数据线、控制线、中断线引脚复用漏掉一个就会出现断断续续的异常。3.3 分区、挂载与读写把 CF 卡当成磁盘用驱动注册成功后CF 卡在 Linux 里就是一个磁盘设备后续操作跟操作一块硬盘没什么区别。插入 CF 卡先看内核日志确认设备名再分区格式化挂载。# 确认内核识别到设备 dmesg | grep -E ata|pcmcia|sd[a-z] # 查看分区表 fdisk -l /dev/sda # 重建分区表第一块 CF 卡通常需要 fdisk /dev/sda # 在 fdisk 里依次输入 n p 1 回车回车 w 完成一个主分区 # 格式化为 ext4 mkfs.ext4 /dev/sda1 # 挂载 mkdir -p /mnt/cf mount /dev/sda1 /mnt/cf # 写入测试文件 echo cf card test /mnt/cf/test.txt sync这个流程里有两个细节容易被忽略。fdisk 建分区时如果 CF 卡之前被塞过 SD 卡镜像分区表会出现 overlay 残留建议先 p 打印确认再用 o 重建空分区表。mkfs.ext4 之前最好执行一遍 blkdiscard但注意部分廉价 CF 控制器不支持 discard报错直接忽略也不影响。格式化完成后用 dmesg 再确认一次设备状态确保读写过程中没有报 I/O 错误。4. CF 驱动调试避坑片选配错、卡死、中断风暴的排查记录4.1 把 CF 当 SD 卡刷镜像插槽完全没反应现象把从 SD 卡读出来的镜像直接 dd 到 CF 卡插到 ARM 板的 CF 槽上系统日志什么也没有fdisk -l 看不到设备。原因CF 卡此时可能处于 Memory 模式或分区表结构不符合 ATA 设备预期。更常见的是镜像里没有适配 CF 槽驱动栈的分区格式Linux 在 ATA 层解析失败后干脆不识别。另外 SD 卡走 mmc_blkCF 卡走 sd_mod两者的设备节点不一样镜像里的 Ubuntu 系统没有为 CF 卡重做 bootloader 和 fstab。解决不要跨介质复制整盘镜像。把 CF 卡插到 ARM 板按 3.3 的流程重新 fdisk 建分区、mkfs.ext4然后把文件系统内容手工拷贝过去。如果你确实需要做一张可启动的 CF 卡分区时用 fdisk /dev/sda别沿用 SD 卡的 mmcblk 设备路径去写 fstab。4.2 设备树片选号配错驱动注册成功但 probe 失败现象内核日志能看到 at91_cf 驱动的 probe 被调用但马上就报 request_mem_region failed 或者一片 0xffffffff 读返回值板子死机或卡死。原因设备树 reg 里的片选号与实际硬件接线不一致。CF 槽在 SoC 外部总线控制器上有独立的片选信号芯片手册里标的是 CS1、CS2 之类但设备树里通常从 0 开始编号。比如硬件接在 CS3而设备树写 reg 0x0 0x0 0x0这时访问的是错误地址。解决先查原理图确认 CF 槽片选接到 SoC 的哪个 EBI/SMC 片选管脚再查芯片手册把片选号映射成 0 开始的编号。改完设备树后用 devmem 直接读该地址验证devmem 0x20000000 32 如果能读到非 0xffffffff 的值说明地址域对了驱动层面的问题还在别处。4.3 CF 卡插上去就卡死中断风暴刷屏现象dmesg 不断打印 irq N: nobody cared 或 spurious interrupt系统响应卡顿严重驱动 probe 无法完成。原因CF 槽的 IRQ 引脚被复用成 GPIO但中断控制器没有正确识别触发条件。CF 卡在 IDE 模式下的中断是电平触发还是边沿触发取决于卡和主控的协商结果很多驱动默认 IRQF_TRIGGER_FALLING而实际的 CF 卡输出的是低电平有效电平信号导致中断一直触达。解决把中断触发类型改成和实际一致。设备树里 interrupt 属性带上触发标记比如 irq 0 IRQ_TYPE_LEVEL_LOW或者在 platform device 请求中断时显式指定 IRQF_TRIGGER_LOW。还有一个更隐蔽的问题IRQ 号没从 GPIO 转到中断控制器号直接用了 GPIO 号这种错误在 log 里表现为 irq 值远大于 NR_IRQS。用 cat /proc/interrupts 对照看看中断号是否合理再用 gpio_to_irq 重新转换。4.4 识别到 /dev/sda 但读写全返回 0xFF 或 CRC 错误现象fdisk -l /dev/sda 能看到容量但读分区表报错dd 读 CF 卡内容全是 0xFFFFFFFF或者 dmesg 里爆一堆 CRC error。原因CF 槽接在外部总线上总线时序和宽位没有配平。CF 卡的 True IDE 模式对片选建立时间、脉冲宽度、访问周期有最低要求很多 ARM SoC 的外部总线控制器默认配置面向 NOR Flash访问周期比 CF 卡要求的短导致数据线采样太早或太晚。另外 16 位 CF 卡如果被配置成 8 位总线访问高字节会读成 0xFF。解决调整外部总线控制器的时序寄存器把读/写周期拉长到 250ns 以上。不同 SoC 寄存器不同S3C24xx 系列是 BANKCON、AT91 是 SMC 的 RPD/RCS/RWE 字段。调完用 dd if/dev/sda of/dev/null bs512 count1 验证不再报 CRC 就能继续。16 位卡与 8 位总线的矛盾需要硬件改线软件一般无解买卡时优先选 16 位 True IDE 模式兼容性好的规格。4.5 同一张卡在 PC 上能读插 ARM 板分区表就乱现象CF 卡在台式机读卡器上识别正常分区数据完整插进 ARM 板 CF 槽fdisk 显示分区表乱码容量比原来小一半或大一半。原因PC 读卡器走 USB 存储协议报告的是 512 字节逻辑扇区大小而 ARM 板上的 ATA 控制器访问 CF 卡时采取不同的扇区翻译方式。还有个常见原因CF 卡之前被格式化成了 4Kn 模式Linux 的 sd_mod 没有启用 4Kn 支持。解决先用 hdparm -I /dev/sda 查看卡的真实扇区大小如果显示 Logical Sector Size 是 4096而 ARM 板的 ATA 控制器只支持 512则需要用配套工具把卡切回 512 模式。部分 CF 卡支持用 hdparm 直接设置hdparm --set-sector-size 512 -s 0 /dev/sda命令执行前一定要备份数据。切回后重新 fdisk 建分区问题消失。5. 验证驱动有没有跑对用吞吐测试和 hdparm 参数判断 CF 卡工作模式驱动能识别设备只是第一步CF 卡的性能是不是达到预期常常被忽略。我拿到一块能出 /dev/sda 的板子会先做一轮基准测试因为很多“驱动能用”的背后掩藏着 DMA 没开、总线降速、IDE 模式协商失败的问题。hdparm 是这里最有用的工具它能直接读出 ATA 设备协商后的工作模式。# 查看 CF 卡完整能力和当前工作模式 hdparm -I /dev/sda # 测试缓存读取速度 hdparm -t /dev/sda # 测试直接 DMA 读取速度过文件系统缓存 dd if/dev/sda of/dev/null bs1M count128 iflagdirecthdparm -I 输出的重点看三处DMA modes 是否显示 udma4 或 udma5而不是 PIO modesSecurity 区域是否锁定锁定的卡会拒绝写入LBA 与 CHS 的容量是否一致。如果 DMA modes 只显示 multword 而没有 udma说明控制器工作在老式 Multiword DMA 模式速度一般只有 16MB/s 级别这时要检查内核有没有给该 ATA 设备开 DMA 通道。dd 的 iflagdirect 参数很关键它绕过页面缓存测出来的是裸读写速度。真 IDE 模式配合 DMA 的 CF 卡读速度应该在 40MB/s 以上如果测出来只有个位数 MB/s多半是 PIO 模式在跑。此时回到 menuconfig 确认 CONFIG_PATA_AT91 里有没有启用 DMA另外检查板子电源能否提供 CF 卡峰值电流——很多 ARM 板的 CF 槽供电只有 3.3V 且限流CF 卡降速自我保护。我现在的习惯是任何新 ARM 板到手一旦确认有 CF 槽优先级排最早先跑一遍 hdparm 和 dd 三连把数据存下来当基线。半年后再测一次如果同一张卡速度下降超过三成优先怀疑卡本身磨损而不是驱动。这套流程帮我在不少项目里筛掉过“能用但不可靠”的驱动配置希望帮到你。本文还有配套的精品资源点击获取
返回列表