Linux MTD (Memory Technology Device) subsystem analysis -For Atheros char device

时间:2024-12-26 15:34:50

Linux MTD (Memory Technology Device) subsystem analysis

For Atheros char device

读了Linux MTD 源代码分析 对这部分有了整体的认识,结合现有代码,分析一下Atheros的MTD是如何使用的。

Linux kernel: 2.6.31.

Atheros platform: QCA9890???

参考Linux MTD 源代码分析,这里把MTD分为4层,从上到下依次是:字符设备节点、字符设备、MTD 核心、FLASH 硬件驱动。

字符设备节点:/dev/mtd0 /dev/mtd1 等节点,可以通过fopen()等直接使用。这些节点是MTD子系统创建的,用户通过访问这些节点操作MTD。

字符设备:MTD子系统把MTD封装为字符设备。

MTD核心:管理所有的MTD设备,提供get/add/del MTD功能;同时,一个MTD设备可能逻辑上会被划分为多个区,每个区被当成一个MTD设备,所以还提供了通过分区信息批量加入、删除MTD设备的功能。为了从kernel启动参数获取分区信息,MTD核心提供了分析启动参数中分区信息的功能。

FLASH硬件驱动:提供FLASH的read/write/erase等功能。因为Atheros的分区信息是在kernel的启动参数里面取得,所以这里还有分析启动参数分区信息的功能;根据分区信息把MTD设备加到MTD核心中。

系统启动时,MTD核心层可以直接编译到kernel中,FLASH驱动层在系统启动时解析分区信息,把MTD加入到MTD核心层,MTD核心层完成设备节点的创建,MTD字符设备注册MTD字符设备信息,这样访问设备节点的时候就可以找到具体的执行函数。

Linux MTD (Memory Technology Device) subsystem analysis -For Atheros char device

图1 Atheros MTD Subsystem Architecture

图1显示了各层的主要信息。

MTD设备的使用是通过访问设备节点/dev/mtd0 /dev/mtd1,一般用法是

File *f = fopen(“/dev/mtd0”, “r”);

 

fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

/dev/mtd0 等设备是由MTD核心创建的,由add_mtd_device()->device_register()创建。fread/fwrite是由MTD字符设备层实现具体的读写,至于是如何找到MTD字符设备层的函数的,以及设备节点如何和这些函数关联的,需要看下fread以及Linux的驱动模型(device_register/register_chrdev),以后再总结吧。

MTD字符设备层是通过register_chrdev()来实现的,需要提供struct file_operations中的函数,这些函数完成对指定MTD的read/write/erase等。register_chrdev()之后,当用fread等访问MTD的时候,就会找到这里注册的函数。

static const struct file_operations mtd_fops = {

            .owner                     = THIS_MODULE,

            .llseek                       = mtd_lseek,

            .read             = mtd_read,

            .write                       = mtd_write,

            .ioctl              = mtd_ioctl,

#ifdef CONFIG_COMPAT

            .compat_ioctl       = mtd_compat_ioctl,

#endif

            .open                       = mtd_open,

            .release        = mtd_close,

            .mmap                    = mtd_mmap,

#ifndef CONFIG_MMU

            .get_unmapped_area = mtd_get_unmapped_area,

#endif

};

 

static int __init init_mtdchar(void)

{

            int status;

 

            status = register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops);

            if (status < 0) {

                       printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",

                              MTD_CHAR_MAJOR);

            }

 

            return status;

}

具体看下open/read/close是如何使用MTD的,get/put_mtd_device有MTD核心层提供,下面会具体看到。mtd_open 通过inode取得设备号,进一步通过get_mtd_device取得MTD的struct mtd_info信息,也就获得了具体操作MTD的函数;以后的mtd_read/write等函数就可以直接使用mtd->read/mtd->write完成具体的操作。

static int mtd_open(struct inode *inode, struct file *file)

{

            int minor = iminor(inode);

            int devnum = minor >> 1;

            …

            mtd = get_mtd_device(NULL, devnum);

    ….

            mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);

            mfi->mtd = mtd;

            file->private_data = mfi;

out:

            unlock_kernel();

            return ret;

} /* mtd_open */

 

static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)

{

            struct mtd_file_info *mfi = file->private_data;

            struct mtd_info *mtd = mfi->mtd;

            …

            while (count) {

                       switch (mfi->mode) {

                       default:

                                   ret = mtd->read(mtd, *ppos, len, &retlen, kbuf);

                       }

            }

    …

            return total_retlen;

} /* mtd_read */

 

static int mtd_close(struct inode *inode, struct file *file)

{

            struct mtd_file_info *mfi = file->private_data;

            struct mtd_info *mtd = mfi->mtd;

            put_mtd_device(mtd);

            file->private_data = NULL;

            kfree(mfi);

            return 0;

} /* mtd_close */

MTD核心层提供的功能可以分为两部分:面向上层用户的API和面向底层用户的API。上层用户主要是指MTD字符设备层,字符设备层可以通过get_mtd_device获得MTD的struct mtd_info 信息,然后通过mtd->read、mtd->write等操作MTD。使用方法可以参考字符设备层中函数的实现。下层用户是FLASH驱动层,FLASH驱动层通过parse_mtd_partitions获得kernel启动参数中的分区信息,然后通过add_mtd_partitions把各个分区当做单独的MTD设备加入到MTD核心层;为了分析kernel启动参数,需要注册parser函数,通过register_mtd_parser实现,这个函数的注册可以由FLASH驱动层完成,也可以编译到kernel中(make menuconfig->Device drivers->MTD support->Command line partition table parsing)。

至于注册的parser是如何得到kernel启动参数的,大概的过程是,bootloader通过bootargs的mtdparts传入分区信息,这个分区信息会被当成kernel的启动参数;kernel通过__setup("mtdparts=", mtdpart_setup) 注册函数mtdpart_setup,mtdpart_setup保存分区信息,parser解析保存的信息,参考(cmdlinepart.c)。

MTD核心层的实现原理是:

每个MTD用一个struct mtd_info描述,所有的mtd_info保存在struct mtd_info *mtd_table[]中,get/add/del_mtd_device都是操作mtd_table。

一个MTD FLASH可以逻辑上分为多个区,每个区当成一个MTD,分区信息用struct mtd_partition描述,通过分区信息加入MTD时,需要知道master MTD的信息,每个分区的信息多数来源于master MTD,master MTD不会加入MTD核心层,只有里面的各个分区会加入核心层,当成单独的MTD。

MTD核心层还有notifier的功能,保存在struct mtd_notifier,所有的notifier保存在一个链表中。

具体可以参考Linux MTD 源代码分析。

FLASH驱动层提供mtd_info中read/write等的具体实现,如果有逻辑分区的话,这个mtd_info就是master MTD。然后调用parser_mtd_partitions 和add_mtd_partitions把逻辑分区加入到MTD核心层。