引:这一段时间一直很忙,同时看着几份不同的项目代码,有bootloader启动画面的添加修改、图形界面的优化和配合中间件小组的开发。中间件移植有一个地方是涉及到MTD分区操作的,花了一两天的时间终于将这部分的来龙去脉弄清楚了。在这里声明:MTD驱动代码我只看过大概,大体上知道流程是怎样走,具体的分析只局限于Flash硬件驱动层(具体flash芯片)和MTD设备层(file operation系列函数)。

介绍下环境及工具: VMWare Station + Debian + samba + nfs + Source Insignt + linux-source-2.6.18 + xxxx.patch

问题描述: fd = open("/dev/mtd4", O_RDWR)失败,而fd = open("/dev/mtd4", O_RDONLY)成功

开始之前,找了一些有关MTD设备的资料来看,有代表性有Jim Zeus的《Linux MTD源代码分析》。我没有很深入去研读,主要是去了解Mtd代码的层结构及几个重要的数据结构,毕竟我的主要任务是配合中间件移植小组将mtd分区读写起来,过于探讨细节对于我来说是一个奢求。

  MTD(memory technology device内存技术设备)是用于访问memory设备(ROM、flash)的Linux的子系统。MTD的主要目的是为了使新的memory设备的驱动更加简单,为此它在硬件和上层之间提供了一个抽象的接口。MTD的所有源代码在/drivers/mtd子目录下。我将CFI接口的MTD设备分为四层(从设备节点直到底层硬件驱动),这四层从上到下依次是:设备节点、MTD设备层、MTD原始设备层和硬件驱动层。

另外我还了解一下cfi的驱动流程。如下:

MTD几个重要的的数据结构

struct mtd_info {
    u_char type;
    u_int32_t flags;
    u_int32_t size; // Total size of the MTD

    /* "Major" erase size for the device. Na茂ve users may take this
     * to be the only erase size available, or may use the more detailed
     * information below if they desire
     */
    u_int32_t erasesize;
    /* Minimal writable flash unit size. In case of NOR flash it is 1 (even
     * though individual bits can be cleared), in case of NAND flash it is
     * one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
     * it is of ECC block size, etc. It is illegal to have writesize = 0.
     * Any driver registering a struct mtd_info must ensure a writesize of
     * 1 or larger.
     */
    u_int32_t writesize;
    u_int32_t oobsize; // Amount of OOB data per block (e.g. 16)

    u_int32_t oobavail; // Available OOB bytes per block

    // Kernel-only stuff starts here.
    char *name;
    int index;

    /* ecc layout structure pointer - read only ! */
    struct nand_ecclayout *ecclayout;

    /* Data for variable erase regions. If numeraseregions is zero,
     * it means that the whole device has erasesize as given above.
     */
    int numeraseregions;
    struct mtd_erase_region_info *eraseregions;
};


struct erase_info {
    struct mtd_info *mtd;
    u_int32_t addr;
    u_int32_t len;
    u_int32_t fail_addr;
    u_long time;
    u_long retries;
    u_int dev;
    u_int cell;
    void (*callback) (struct erase_info *self);
    u_long priv;
    u_char state;
    struct erase_info *next;
};

struct mtd_erase_region_info {
    u_int32_t offset;            /* At which this region starts, from the beginning of the MTD */
    u_int32_t erasesize;        /* For this region */
    u_int32_t numblocks;        /* Number of blocks of erasesize in this region */
    unsigned long *lockmap;        /* If keeping bitmap of locks */
};

如果想了解一份驱动代码的流程及细节,先从这些重要的数据结构入手会快得多。

我当时要了解MTD代码是因为fd = open("/dev/mtd4", O_RDWR)返回-1,而fd = open("/dev/mtd4", O_RDONLY)是成功的。可知mtd4是不可写的,但是中间件的移植要读写该mtd分区的,所以必须从驱动入手修改。以下的流程是我已经整理过的,当初并没有那么简单和条理。

首先,从mtd设备层入手,因为open、close、read、write、ioctl等file operation函数都定义在这里。在mtdopen函数的实现中,很快可以看出苗头,注意如下代码框中字体加粗部分:

DE>/* You can't open the RO devices RW */
    if ((file->f_mode & 2) && (minor & 1))
        return -EACCES;

    mtd = get_mtd_device(NULL, devnum);

    if (!mtd)
        return -ENODEV;

    if (MTD_ABSENT == mtd->type) {
        put_mtd_device(mtd);
        return -ENODEV;
    }

    /* You can't open it RW if it's not a writeable device */
    if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) {
        put_mtd_device(mtd);
        return -EACCES;
    }DE>

另外在fs.h中有定义

DE>#define FMODE_READ 1 #define FMODE_WRITE 2DE> 显然,如果open的文件标志有写标记的话,那么会在open时判断该设备是否允许写操作。刚开始我根据if ((file->f_mode & 2) && (minor & 1))去追踪mtd4的次设备号与1相与是否非0。在目标板系统上执行ls -l /dev/mtd4,得知mtd4的次设备号为8,从而确定了不会在这一步返回。

剩下的只有if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE))了,mtd->flags有可能置!MTD_WRITEABLE标志,导致在这里open失败。之后的工作就定位在mtd->flags上。

mtd_info的初始化应该在flash硬件驱动层进行的,本着这样的想法,我特地去了解一下cfi的probe过程,事实证明那是没有必要的。现在回到flash硬件驱动层,找到我们项目的flash芯片的驱程,init_xxxx_map()这样定义:

DE>    //以上是mtd各个区的offset、size设置
    xxxx_mtd = do_map_probe("cfi_probe", &xxxx_map);
    if (!xxxx_mtd) {
        iounmap((void *)xxxx_map.virt);
        return -ENXIO;
    }
        
    add_mtd_partitions(xxxx_mtd, xxxx_parts, numparts);
    xxxx_mtd->owner = THIS_MODULE;
    return 0;DE>

mtd_info的最初初始化是在do_map_probe()里完成的,它其实是通过调用cfi_probe()来完成这些工作的,从那里开始追踪可以看到mtd->flags = MTD_CAP_NORFLASH,而MTD_CAP_NORFLASH则等于MTD_WRITEABLE | MTD_BIT_WRITEABLE。add_mtd_partitions()是mtd partitions的加载实现,一般来说mtd_info等信息也会在这里被设置,进入该函数,果然可以找到我们关心的部分:

DE>        if ((slave->mtd.flags & MTD_WRITEABLE) &&
         (slave->offset % slave->mtd.erasesize)) {
            /* Doesn't start on a boundary of major erase size */
            /* FIXME: Let it be writable if it is on a boundary of _minor_ erase size though */
            slave->mtd.flags &= ~MTD_WRITEABLE;
            printk ("mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n",
                parts[i].name);
        }
        if ((slave->mtd.flags & MTD_WRITEABLE) &&
         (slave->mtd.size % slave->mtd.erasesize)) {
            slave->mtd.flags &= ~MTD_WRITEABLE;
            printk ("mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n",
                parts[i].name);
        }DE>

这两个if判断,如果分区的偏移量offset不能被erasesize整除或者分区的大小size不能被erasesize整除的话,那么该分区的mtd.flags会被置不可写标志。从那些printk信息来看,offset、size必须满足erasesize的边界条件,才允许可写。

找了原因,我验证了一下。ioctl(fd, MEMGETINFO, &mtd_info)得到mtd_info.erasesize=0x20000,xxxx-flash.c定义mtd分区信息是:

DE>    { name: "rootfs",        offset: 0,            size: 28*1024*1024 },
    { name: "bootloader",     offset: 0x01C00000, size: 512*1024 },
    { name: "zImage",    offset: 0x01C80000, size: 3582*1024 },
    { name: "macadr",    offset: 0x01FFF800,    size: 144 },
    { name: "config",    offset: 0x01FFF890,    size: 1904 },DE>

可以计算到rootfs、bootloader分区的offset、size符合erasesize的边界条件,zImage、config、macadr分区则不符合erasesize的边界条件。以O_RDWR标志open /dev/mtd0和/dev/mtd1都是成功的,open后三个mtd区设备则失败,基本上验证了之前的分析。