Linux MTD系统层次
引入了MTD后,linux中的flash设备驱动及接口分为四个层,从上到下:设备节点,MTD设备层,MTD原始设备层,硬件驱动层。
1. 硬件驱动层:FLASH硬件驱动层负责FLASH硬件设备的读、写、擦出,LINUX MTD设备的NOR FLASH驱动位于/driver/mtd/chips子目录下,NAND FLASH驱动则位于/driver/mtd/nand子目录下。
2. MTD原始设备层:MTD原始设备层由两部分构成,一部分是MTD原始设备的通用代码(mtdcore.c、mtdpart.c),另一部分是各个特定的FLASH的数据,例如分区。
3. MTD设备层:基于MTD原始设备,LINUX系统可以定义出MTD的块设备(主设备号31)www.linuxidc.com和字符设备(设备号90),构成设备层。MTD字符设备在mtdchar.c实现,MTD块设备在mtdblock.c实现。
4. 设备节点:通过mknod在/dev子目录下建立MTD字符设备节点(主设备号为90)和块设备节点(主设备号为31),用户通过访问此设备节点即可访问MTD字符设备和块设备。
从上图可以看出,MTD设备层与原始设备层打交道。通过分析源代码我们可以知道当上层要求对FLASH进行读写时,它会像设备层发出请求,设备层的读写函数会调用原始设备层中的读写函数,即mtd_info结构体(mtd原始设备层中描述设备的专用结构体)中的读写函数,而mtd_info中的函数会调用nand_chip(nand硬件驱动层中描述设备的结构体,其中包含了针对特定设备的基本参数和设备操作函数)中的读写函数。
所以当我们写一个flash硬件驱动程序时,有以下步骤:
1. 如果FLASH要分区,则定义mtd_partition数组,将FLASH分区信息记录其中。
2. 在模块加载时为每一个chip(主分区)分配mtd_info和nand_chip的内存,根据目标板nand 控制器的特殊情况初始化nand_chip中的实现对FLASH操作的成员函数,如hwcontrol()、dev_ready()、read_byte()、write_byte()等。填充mtd_info,并将其priv成员指向nand_chip。
3. 以mtd_info为参数调用nand_scan()函数探测NAND FLASH的存在。nand_scan()函数会从FLASH芯片中读取其参数,填充相应nand_chip成员。
4. 如果要分区,则以mtd_info和mtd_partition为参数调用add_mtd_partions(),添加分区信息。在这个函数里面会为每一个分区(不包含主分区)分配一个mtd_info结构体填充,并注册。
struct mtd_info {
u_char type;//内存技术的类型
u_int32_t flags;//标志位
u_int32_t size; // Total size of the MTD
u_int32_t erasesize;
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;
struct nand_ecclayout *ecclayout;
int numeraseregions;
struct mtd_erase_region_info *eraseregions;
int (*erase) (struct mtd_info *mtd, struct erase_info *instr);
int (*point) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys);
void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);
int (*read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);
int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len);
int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf);
int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);
int (*writev) (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count, loff_t to, size_t *retlen);
void (*sync) (struct mtd_info *mtd);
int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len);
int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len);
/* 能量管理函数*/
int (*suspend) (struct mtd_info *mtd);
void (*resume) (struct mtd_info *mtd);
int (*block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);
struct notifier_block reboot_notifier;
struct mtd_ecc_stats ecc_stats;
int subpage_sft;
void *priv;
struct module *owner;
int usecount;
int (*get_device) (struct mtd_info *mtd);
void (*put_device) (struct mtd_info *mtd);
};
MTD用户控件编程
Mtdchar.c实现了字符设备接口。通过read(),write()系统调用可读写flash,通过一系列IOCTL命令可获取flash设备信息,擦除flash,读写NAND的OOB,获取OOBlayout及检查NAND坏块等。
static int mtd_ioctl(struct inode *inode, struct file *file,
u_int cmd, u_long arg)
{
struct mtd_info *mtd = (struct mtd_info *)file->private_data;
int ret = 0;
u_long size;
DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n");
size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
if (cmd & IOC_IN) {
ret = verify_area(VERIFY_READ, (char *)arg, size);
if (ret) return ret;
}
if (cmd & IOC_OUT) {
ret = verify_area(VERIFY_WRITE, (char *)arg, size);
if (ret) return ret;
}
switch (cmd) {
case MEMGETREGIONCOUNT:
if (copy_to_user((int *) arg, &(mtd->numeraseregions), sizeof(int)))
return -EFAULT;
break;
…
}
NOR flash设备编程直接利用Mtdchar.c定义的设备接口,利用mtd_info中的成员函数来操作。但NAND Flash有自己的驱动内核,通过/driver/mtd/nand/nand_base.c实现,不再用mtd_info数据结构,而转到了nand_chip数据结构。
NOR Flash驱动程序实例分析
#define WINDOW_ADDR 0x01000000 /* NOR FLASH物理地址 */
#define WINDOW_SIZE 0x800000 /* NOR FLASH大小 */
#define BUSWIDTH 2
/* 探测的接口类型,可以是"cfi_probe", "jedec_probe", "map_rom", NULL }; */
#define PROBETYPES { "cfi_probe", NULL }
#define MSG_PREFIX "S3C2410-NOR:" /* prefix for our printk()'s */
#define MTDID "s3c2410-nor" /* for mtdparts= partitioning */
static struct mtd_info *mymtd;
struct map_info s3c2410nor_map = // map_info
{
.name = "NOR flash on S3C2410",
.size = WINDOW_SIZE,
.bankwidth = BUSWIDTH,
.phys = WINDOW_ADDR,
};
#ifdef CONFIG_MTD_PARTITIONS
/* MTD分区信息 */
static struct mtd_partition static_partitions[] =
{
{
.name = "BootLoader", .size = 0x040000, .offset = 0x0 //bootloader存放的区域
} ,
{
.name = "Kernel", .size = 0x0100000, .offset = 0x40000 //内核映像存放的区域
}
,
{
.name = "RamDisk", .size = 0x400000, .offset = 0x140000 //ramdisk存放的区域
}
,
{
.name = "cramfs(2MB)", .size = 0x200000, .offset = 0x540000 //只读的cramfs区域
}
,
{
.name = "jffs2(0.75MB)", .size = 0xc0000, .offset = 0x740000 //可读写的jffs2区域
}
,
};
#endif
static int mtd_parts_nb = 0;
static struct mtd_partition *mtd_parts = 0;
int __init init_s3c2410nor(void)
{
static const char *rom_probe_types[] = PROBETYPES;
const char **type;
const char *part_type = 0;
printk(KERN_NOTICE MSG_PREFIX "0x%08x at 0x%08x\n", WINDOW_SIZE, WINDOW_ADDR);
s3c2410nor_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE);//物理->虚拟地址
if (!s3c2410nor_map.virt)
{
printk(MSG_PREFIX "failed to ioremap\n");
return - EIO;
}
simple_map_init(&s3c2410nor_map);
mymtd = 0;
type = rom_probe_types;
for (; !mymtd && *type; type++)
{
mymtd = do_map_probe(*type, &s3c2410nor_map);//探测NOR FLASHdo_map_probe()
//定义在drivers/mtd/chips/chipreg.c中
}
if (mymtd)
{
mymtd->owner = THIS_MODULE;
#ifdef CONFIG_MTD_PARTITIONS
mtd_parts_nb = parse_mtd_partitions(mymtd, NULL, &mtd_parts, MTDID);//探测分区信息
if (mtd_parts_nb > 0)
part_type = "detected";
if (mtd_parts_nb == 0) //未探测到,使用数组定义的分区信息
{
mtd_parts = static_partitions;
mtd_parts_nb = ARRAY_SIZE(static_partitions);
part_type = "static";
}
#endif
add_mtd_device(mymtd);
if (mtd_parts_nb == 0)
printk(KERN_NOTICE MSG_PREFIX "no partition info available\n");
else
{
printk(KERN_NOTICE MSG_PREFIX "using %s partition definition\n",
part_type);
add_mtd_partitions(mymtd, mtd_parts, mtd_parts_nb);//添加分区信息
}
return 0;
}
iounmap((void*)s3c2410nor_map.virt);
return - ENXIO;
}
static void __exit cleanup_s3c2410nor(void)
{
if (mymtd)
{
del_mtd_partitions(mymtd); //删除分区
del_mtd_device(mymtd); //删除设备
map_destroy(mymtd);
}
if (s3c2410nor_map.virt)
{
iounmap((void*)s3c2410nor_map.virt);
s3c2410nor_map.virt = 0;
}
}
NAND flash驱动程序实例分析
我们以2.6.26内核中s3c2410的nand flash驱动程序为例来分析一下这个过程,这里的flash驱动被写成了platform驱动的形式。我们下面分析其过程:
1. 注册nand flash设备
nand flash分区:
linux2.6.26.8/arch/arm/plat-s3c24xx/common-smdk.c:
static struct mtd_partition smdk_default_nand_part[] = {
[0] = {
name: "bootloader",
size: 0x00100000,
offset: 0x0,
},
[1] = {
name: "kernel",
size: 0x00300000,
offset: 0x00100000,
},
[2] = {
name: "root",
size: 0x02800000,
offset: 0x00400000,
},
};
static struct s3c2410_nand_set smdk_nand_sets[] = { //该数组为chip集合,这里我们只有一片chip
[0] = {
.name = "NAND",
.nr_chips = 1,
.nr_partitions = ARRAY_SIZE(smdk_default_nand_part),
.partitions = smdk_default_nand_part,
},
};
static struct s3c2410_platform_nand smdk_nand_info = { //这里将许多数据作为platform_data传入包括chip数组
.tacls = 20,
.twrph0 = 60,
.twrph1 = 20,
.nr_sets = ARRAY_SIZE(smdk_nand_sets),
.sets = smdk_nand_sets,
};
nand控制器资源:
linux2.6.26.8/arch/arm/plat-s3c24xx/devs.c
static struct resource s3c_nand_resource[] = {
[0] = {
.start = S3C2410_PA_NAND,
.end = S3C2410_PA_NAND + S3C24XX_SZ_NAND - 1,
.flags = IORESOURCE_MEM,
}
};
struct platform_device s3c_device_nand = {
.name = "s3c2410-nand",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_nand_resource),
.resource = s3c_nand_resource,
};
注册nand flash作为platform device:
linux2.6.26.8/arch/arm/plat-s3c24xx/common-smdk.c:
static struct platform_device __initdata *smdk_devs[] = {
&s3c_device_nand,
…
};
void __init smdk_machine_init(void)
{
…
s3c_device_nand.dev.platform_data = &smdk_nand_info; //注意这里的赋值,在nand flash驱动程序的probe函数里面利用了这里赋值的数据
platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs));
s3c2410_pm_init();
}
2.注册nand flash driver
linux/drivers/mtd/nand/s3c2410.c:
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,
},
};
static int __init s3c2410_nand_init(void)
{
printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");
platform_driver_register(&s3c2412_nand_driver);
platform_driver_register(&s3c2440_nand_driver);
return platform_driver_register(&s3c2410_nand_driver);
}
module_init(s3c2410_nand_init);
当platform_driver驱动被加载时或者是当platform_device被注册时,总线驱动程序
会查找与设备匹配的驱动程序,找到时设备驱动程序的probe函数会被调用,下面我们来分析一下在我们驱动程序中的probe函数:
static int s3c2410_nand_probe(struct platform_device *dev)
{
return s3c24xx_nand_probe(dev, TYPE_S3C2410);
}
static int s3c24xx_nand_probe(struct platform_device *pdev,
enum s3c_cpu_type cpu_type)
{
struct s3c2410_platform_nand *plat = to_nand_plat(pdev);
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", pdev);
info = kmalloc(sizeof(*info), GFP_KERNEL); //分配s3c2410_nand_info内存
if (info == NULL) {
dev_err(&pdev->dev, "no memory for flash info\n");
err = -ENOMEM;
goto exit_error;
}
memzero(info, sizeof(*info)); //将s3c2410_nand_info清零
platform_set_drvdata(pdev, info); //pdev->dev->driver_data = info
spin_lock_init(&info->controller.lock);
init_waitqueue_head(&info->controller.wq);
info->clk = clk_get(&pdev->dev, "nand");
if (IS_ERR(info->clk)) {
dev_err(&pdev->dev, "failed to get clock\n");
err = -ENOENT;
goto exit_error;
}
clk_enable(info->clk);
res = pdev->resource;
size = res->end - res->start + 1;
info->area = request_mem_region(res->start, size, pdev->name);
if (info->area == NULL) {
dev_err(&pdev->dev, "cannot reserve register region\n");
err = -ENOENT;
goto exit_error;
}
info->device = &pdev->dev;
info->platform = plat;
info->regs = ioremap(res->start, size); //存储nand控制器寄存器虚拟地
址
info->cpu_type = cpu_type;
if (info->regs == NULL) {
dev_err(&pdev->dev, "cannot reserve register region\n");
err = -EIO;
goto exit_error;
}
dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs);
err = s3c2410_nand_inithw(info, pdev); //设置TACLS TWRPH0 TWRPH1
if (err != 0)
goto exit_error;
sets = (plat != NULL) ? plat->sets : NULL; //sets指向plat->sets数组的首地址
nr_sets = (plat != NULL) ? plat->nr_sets : 1; //plat->sets中的chips数目
info->mtd_count = nr_sets;
size = nr_sets * sizeof(*info->mtds);
info->mtds = kmalloc(size, GFP_KERNEL);
if (info->mtds == NULL) {
dev_err(&pdev->dev, "failed to allocate mtd storage\n");
err = -ENOMEM;
goto exit_error;
}
memzero(info->mtds, size); //将申请的s3c2410_nand_mtd结构体数组清零
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); //初始化s3c2410_nand_mtd结构
体中的chip成员和mtd成员,且mtd.priv = chip
nmtd->scan_res = nand_scan_ident(&nmtd->mtd,
(sets) ? sets->nr_chips : 1); //设置nand_chip一些成员
的默认值并探测FLASH,并读出FLASH参数,填入nand_chip
if (nmtd->scan_res == 0) {
s3c2410_nand_update_chip(info, nmtd); //
nand_scan_tail(&nmtd->mtd); //设置nand_chip中所有未被设置的
函数指针的值,并填充相关mtd_info成员,若需要建立bad block table
s3c2410_nand_add_partition(info, nmtd, sets); //添加分区
}
if (sets != NULL)
sets++; //注意这里sets++,指向下一个plat->sets里的set
}
if (allow_clk_stop(info)) {
dev_info(&pdev->dev, "clock idle support enabled\n");
clk_disable(info->clk);
}
pr_debug("initialised ok\n");
return 0;
exit_error:
s3c2410_nand_remove(pdev);
if (err == 0)
err = -EINVAL;
return err;
}