上一节已经了解了norfalsh的用法。这一节熟悉下norflash(xip结构)的用法。
首先看下图,MTD层已经帮我们做好了如何擦除falsh,怎么读flash,怎么写flash。我们写驱动主要就是告诉kernel,我们的norfalsh相关信息,来匹配MTD层函数。
块设备怎么读写,比如应用层open,write后发生什么,主要就是通过文件系统调用了ll_rw_block;具体见下面文章介绍怎么操作块设备的。https://blog.csdn.net/dachunfree/article/details/80595044
norflash驱动代码如下:
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/partitions.h>
#include <asm/io.h>
static struct map_info *s3c_nor_map;
static struct mtd_info *s3c_nor_mtd;
static struct mtd_partition s3c_nor_parts[] = {
[0] = {
.name = "bootloader_nor",
.size = 0x00040000,
.offset = 0,
},
[1] = {
.name = "root_nor",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
}
};
static int s3c_nor_init(void)
{
/* 1. 分配map_info结构体 */
s3c_nor_map = kzalloc(sizeof(struct map_info), GFP_KERNEL);;
/* 2. 设置: 物理基地址(phys), 大小(size), 位宽(bankwidth), 虚拟基地址(virt) */
s3c_nor_map->name = "s3c_nor";
s3c_nor_map->phys = 0;
s3c_nor_map->size = 0x1000000; /* >= NOR的真正大小 */
s3c_nor_map->bankwidth = 2;
s3c_nor_map->virt = ioremap(s3c_nor_map->phys, s3c_nor_map->size);
simple_map_init(s3c_nor_map);
/* 3. 使用: 调用NOR FLASH协议层提供的函数来识别 */
printk("use cfi_probe\n");
s3c_nor_mtd = do_map_probe("cfi_probe", s3c_nor_map);
if (!s3c_nor_mtd)
{
printk("use jedec_probe\n");
s3c_nor_mtd = do_map_probe("jedec_probe", s3c_nor_map);
}
if (!s3c_nor_mtd)
{
iounmap(s3c_nor_map->virt);
kfree(s3c_nor_map);
return -EIO;
}
/* 4. add_mtd_partitions */
add_mtd_partitions(s3c_nor_mtd, s3c_nor_parts, 2);
return 0;
}
static void s3c_nor_exit(void)
{
del_mtd_partitions(s3c_nor_mtd);
iounmap(s3c_nor_map->virt);
kfree(s3c_nor_map);
}
module_init(s3c_nor_init);
module_exit(s3c_nor_exit);
MODULE_LICENSE("GPL");
首先分配map_info结构体,里面包含了对norflash(xip结构)的基本描述:大小,数据总线位宽,虚拟地址等信息。然后通过do_map_probe函数传入:
//主要通过cfi和jedec获取norfalsh的信息。通过name传入
struct mtd_info *do_map_probe(const char *name, struct map_info *map)
drv = get_mtd_chip_driver(name); //判断jedec还是cfi,对应不同的方式和驱动
list_for_each(pos, &chip_drvs_list) //cfi和jedec驱动list 加入到chip_drvs_list
ret = drv->probe(map); //调用probe函数。这个probe可能是cfi驱动或者jedec驱动。看name传入
再来分析chip_drvs_list这个链表是谁list_add 的:
再来搜索都谁注册了这个驱动:可以看到是cfi_probe和jedec_probe
1.先看jedec_probe
mtd_do_chip_probe(map, &jedec_chip_probe);
cfi = genprobe_ident_chips(map, cp);
cp->probe_chip //函数指针,调用jedec_probe_chip函数判断
/***********************jedec_probe_chip**********************************/
cfi_send_gen_cmd(0xaa, //三条指令解锁获取厂家ID和设备ID,看芯片手册
cfi_send_gen_cmd(0x55,
cfi_send_gen_cmd(0x90,
//获取厂家ID和设备ID
cfi->mfr = jedec_read_mfr(map, base, cfi);
cfi->id = jedec_read_id(map, base, cfi);
//和jedec驱动中的数组比对,找到相关falsh信息
for (i = 0; i < ARRAY_SIZE(jedec_table); i++)
if ( jedec_match( base, map, cfi, &jedec_table[i] ) ) //dedec_table
//regions,扇区(blocks),如下
{
.mfr_id = MANUFACTURER_MACRONIX,
.dev_id = MX29LV160T,
.name = "MXIC MX29LV160T",
.uaddr = {
[0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
[1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
},
.DevSize = SIZE_2MiB,
.CmdSet = P_ID_AMD_STD,
.NumEraseRegions= 4,
.regions = {
ERASEINFO(0x10000,31),
ERASEINFO(0x08000,1),
ERASEINFO(0x02000,2),
ERASEINFO(0x04000,1)
}
}
/********************************jedec 匹配完成*************************************/
mtd = check_cmd_set(map, 1); //上面的数组信息,会传给map,开始实现mtd层函数
cfi_cmdset_0001(map, primary);
mtd->erase = cfi_intelext_erase_varsize;
mtd->read = cfi_intelext_read;
mtd->write = cfi_intelext_write_words;
mtd->sync = cfi_intelext_sync;
mtd->lock = cfi_intelext_lock;
mtd->unlock = cfi_intelext_unlock;
mtd->suspend = cfi_intelext_suspend;
mtd->resume = cfi_intelext_resume;
mtd->flags = MTD_CAP_NORFLASH;
mtd->name = map->name;
mtd->writesize = 1;
2.再分析cif_probe。
其实和jedec区别就是:cfi不通过数组方式获取flash信息,而是通过命令 获取。具体见下链接。
https://blog.csdn.net/dachunfree/article/details/83652594
mtd_do_chip_probe(map, &cfi_chip_probe);
cfi = genprobe_ident_chips(map, cp);
cp->probe_chip //函数指针,调用cfi_probe_chip
/****************************cfi_probe_chip******************************/
cfi_send_gen_cmd(0x98, 0x55, //进入cfi模式
qry_present(map,base,cfi) //验证是否可以正确读取QRY
cfi_chip_setup
cfi->cfiq->P_ID = le16_to_cpu(cfi->cfiq->P_ID);
cfi->cfiq->P_ADR = le16_to_cpu(cfi->cfiq->P_ADR);
cfi->cfiq->A_ID = le16_to_cpu(cfi->cfiq->A_ID);
cfi->cfiq->A_ADR = le16_to_cpu(cfi->cfiq->A_ADR);
cfi->cfiq->InterfaceDesc = le16_to_cpu(cfi->cfiq->InterfaceDesc);
cfi->cfiq->MaxBufWriteSize = le16_to_cpu(cfi->cfiq->MaxBufWriteSize);
/***************************************cfi end*******************************/
check_cmd_set
cfi_cmdset_0001 //开始往MTD info中写入相关操作函数
mtd->erase = cfi_intelext_erase_varsize;
mtd->read = cfi_intelext_read;
mtd->write = cfi_intelext_write_words;
mtd->sync = cfi_intelext_sync;
mtd->lock = cfi_intelext_lock;
mtd->unlock = cfi_intelext_unlock;
mtd->suspend = cfi_intelext_suspend;
mtd->resume = cfi_intelext_resume;
mtd->flags = MTD_CAP_NORFLASH;
mtd->name = map->name;
mtd->writesize = 1;
现在读写擦除等函数已经实现好了(实际就做了个匹配而已)。最后一步:
int add_mtd_partitions(struct mtd_info ,struct mtd_partition *parts,int nbparts)
add_mtd_device(&slave->mtd);
list_for_each(this, &mtd_notifiers)
not->add(mtd);//遍历mtd_notifiers链表,调用add函数指针
/***********************************************************************************/
register_mtd_blktrans(struct mtd_blktrans_ops *tr)
register_mtd_user(&blktrans_notifier);
list_add(&blktrans_notifier->list, &mtd_notifiers);//找到mtd_notifiers链表添加出
blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);//mtd_blkrans是注册执行函数。理论执行电梯调度算法和读写函数都在此。这个只是唤醒下个线程.
tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr,); //线程执行函数
res = do_blktrans_request(tr, dev, req);
case READ:
tr->readsect(dev, block, buf//调度执行函数,readsect
/***********************************************************************************/
static struct mtd_notifier blktrans_notifier = {
.add = blktrans_notify_add,//这就是not->add(mtd)调用的函数,如下所示:
}
blktrans_notify_add(struct mtd_info *mtd)
list_for_each(this, &blktrans_majors)
tr->add_mtd(tr, mtd);//最终调用add_mtd,继续搜索
.add_mtd = mtdblock_add_mtd,//mtdblockro.c中找到
struct mtd_blktrans_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
dev->mtd = mtd;//将我们开始做好的读写函数(再mtd里面),赋给dev结构体
add_mtd_blktrans_dev(dev);
gd = alloc_disk(1 << tr->part_bits);//分配gendisk结构体
set_capacity(gd, (new->size * tr->blksize) >> 9);//设置容量
add_disk(gd);//添加驱动
最后这一步,最重要的就是 blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);这个函数,mtd_blktrans_request唤醒了那个线程是真正的执行电梯合并调度算法的处理。
我们执行read,write时候,最终会调用ll_rw_block函数。最终就是调用blk_init_queue中的函数指针。