linux下块设备驱动程序
块设备不能向字符设备那样访问,而是要先将请求放入队列,优化调整顺序后再执行,这种访问方式称为"电梯调度算法"。
本篇文章通过ramdisk、nand flash、nor flash来讲解如何写块设备驱动程序。
一、ramdisk
1.因为块设备驱动程序是将请求放入队列然后调整顺序后执行,所以我们需要先定义请求队列:
static unsigned char *ramblock_buf; ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL); static DEFINE_SPINLOCK(ramblock_lock); struct request_queue_t *ramblock_queue = blk_init_queue(do_ramblock_request,&ramblock_lock);//提供读写函数和自旋锁 static void do_ramblock_request(request_queue_t *q) { struct request *req; while ((req == elv_next_request(q) != NULL)) { unsigned long offset = req->sector * 512; unsigned long len = req->current_nr_sectors * 512; if (rq_data_dir(req) == READ) { memcpy(req->buffer,ramblock_buf+offset, len); } else { memcpy(ramblock_buf+offset, req->buffer, len); } end_request(req, 1); } }
request结构体的主要成员包括:
sector_t sector;第一个尚未传输的扇区
unsigned long nr_sectors;尚待完成的扇区数
unsigned int current_nr_sectors;当前I/O操作中待完成的扇区数
驱动中会经常与这3个成员打交道,这3个成员在内核和驱动交互中发挥着重大作用。它们以512字节大小为一个扇区,如果硬件的扇区大小不是512字节,则需要进行相应的调整。例如,如果硬件的扇区大小是2048字节,则在进行硬件操作之前,需要用4来除起始扇区号。
sector_t sector;第一个尚未传输的扇区
unsigned long nr_sectors;尚待完成的扇区数
unsigned int current_nr_sectors;当前I/O操作中待完成的扇区数
驱动中会经常与这3个成员打交道,这3个成员在内核和驱动交互中发挥着重大作用。它们以512字节大小为一个扇区,如果硬件的扇区大小不是512字节,则需要进行相应的调整。例如,如果硬件的扇区大小是2048字节,则在进行硬件操作之前,需要用4来除起始扇区号。
2.注册块设备,并让系统为我们自动分配主设备号:
static int major; major = register_blkdev(0,"ramblock");
3.构造block_device_operations结构体:
#define RAMBLOCK_SIZE (1024*1024) struct blcok_device_operations = rameblcok_fops = { .owner = THIS_MODULE, .getgeo = ramblock_getgeo, }; static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo) { /* 容量=heads*cylinders*sectors*512 */ geo->heads = 2; geo->cylinders = 32; geo->sectors = RAMBLOCK_SIZE/2/32/512; return 0; }
4.分配设置gendisk结构体:
struct gendisk *ramblock_disk = alloc_disk(16);/* 次设备号个数:分区个数+1 */ ramblock_disk->major = major ramblock_disk->first_minor = 0; sprintf(ramblcok_disk->disk_name, "ramblock"); ramblcok_disk->fops = &ramblcok_fops; set_capacity(ramblock_disk, RAMBLOCK_SIZE/512); add_disk(ramblock_disk);
5.测试:
(1) insmod ramblock.ko
(2) ls /dev/ramblock* 信息如下:brw-rw---- 1 0 0 254, 0 Jan 1 02:26 /dev/ramblock
(2) ls /dev/ramblock* 信息如下:brw-rw---- 1 0 0 254, 0 Jan 1 02:26 /dev/ramblock
(3)分区:fdisk /dev/ramblock
输入:m 可以查看帮助信息
输入:n 用于创建新的分区
输入:p 用于建立主分区
然后根据提示可以创建分区
输入:m 可以查看帮助信息
输入:n 用于创建新的分区
输入:p 用于建立主分区
然后根据提示可以创建分区
二、Nand flash
1.定义S3C2440的Nand flash控制寄存器结构体:
struct s3c_nand_regs { unsigned long nfconf ; unsigned long nfcont ; unsigned long nfcmd ; unsigned long nfaddr ; unsigned long nfdata ; unsigned long nfeccd0 ; unsigned long nfeccd1 ; unsigned long nfeccd ; unsigned long nfstat ; unsigned long nfestat0; unsigned long nfestat1; unsigned long nfmecc0 ; unsigned long nfmecc1 ; unsigned long nfsecc ; unsigned long nfsblk ; unsigned long nfeblk ; }; static struct s3c_nand_regs *s3c_nand_regs; s3c_nand_regs = ioremap(0x4E000000, sizeof(struct s3c_nand_regs));
2.手动定义分区:
static struct mtd_partition s3c_nand_parts[] = { [0] = { .name = "bootloader", .size = 0x00040000, .offset = 0, }, [1] = { .name = "params", .offset = MTDPART_OFS_APPEND, .size = 0x00020000, }, [2] = { .name = "kernel", .offset = MTDPART_OFS_APPEND, .size = 0x00200000, }, [3] = { .name = "root", .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL, } };
3.使能S3C2440的Nand flash控制器的时钟:
struct clk *clk = clk_get(NULL, "nand"); clk_enable(clk); /* HCLK=100MHz * TACLS: 发出CLE/ALE之后多长时间才发出nWE信号, 从NAND手册可知CLE/ALE与nWE可以同时发出,所以TACLS=0 * TWRPH0: nWE的脉冲宽度, HCLK x ( TWRPH0 + 1 ), 从NAND手册可知它要>=12ns, 所以TWRPH0>=1 * TWRPH1: nWE变为高电平后多长时间CLE/ALE才能变为低电平, 从NAND手册可知它要>=5ns, 所以TWRPH1>=0 */ #define TACLS 0 #define TWRPH0 1 #define TWRPH1 0 s3c_nand_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4); /* NFCONT: * BIT1-设为1, 取消片选 * BIT0-设为1, 使能NAND FLASH控制器 */ s3c_nand_regs->nfcont = (1<<1) | (1<<0);
4.分配nand_chip和mtd_info结构体,并初始化:
struct nand_chip *s3c_nand = kzmalloc(sizeof(struct nand_chip), GFP_KERNEL); struct mtd_info *s3c_mtd = kzmalloc(sizeof(struct mtd_info), GFP_KERNEL); s3c_nand->select_chip = s3c2440_select_chip; s3c_nand->cmd_ctrl = s3c2440_cmd_ctrl; s3c_nand->IO_ADDR_R = &s3c_nand_regs->nfdata; s3c_nand->IO_ADDR_W = &s3c_nand_regs->nfdata; s3c_nand->dev_ready = s3c2440_dev_ready; s3c_nand->ecc.mode = NAND_ECC_SOFT; s3c_mtd->owner = THIS_MODULE; s3c_mtd->priv = s3c_nand; nand_scan(s3c_mtd,1);//识别Nand flash add_mtd_partitions(s3c_mtd, s3c_nand_parts, 4); static void s3c2440_select_chip(struct mtd_info *mtd, int chipnr) { if (chipnr == -1) { /* 取消选中: NFCONT[1]设为1 */ s3c_nand_regs->nfcont |= (1<<1); } else { /* 选中: NFCONT[1]设为0 */ s3c_nand_regs->nfcont &= ~(1<<1); } } /* *当ALE为高电平时传输的是地址, *当CLE为高电平时传输的是命令 *当ALE和CLE都为低电平时传输的是数据 */ static void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl) { if (ctrl & NAND_CLE) { /* 发命令: NFCMMD=dat */ s3c_nand_regs->nfcmd = dat; } else { /* 发地址: NFADDR=dat */ s3c_nand_regs->nfaddr = dat; } } static int s3c2440_dev_ready(struct mtd_info *mtd) { return (s3c_nand_regs->nfstat & (1<<0)); }
5.测试:
使用mtd-utils-05.07.23.tar.bz2
三、Nor flash
1.分配map_info和mtd_info结构体并初始化:
static struct map_info *s3c_nor_map; static struct mtd_info *s3c_nor_mtd; s3c_nor_map = kzalloc(sizeof(struct map_info), GFP_KERNEL);
/* 设置:位宽、物理基地址、虚拟基地址、大小 */
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);//这里面设置了读写和擦除等函数
2.手动分区:
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, } };
3.探测Nor flash:
/* 使用cfi协议探测NOR Flash */ s3c_nor_mtd = do_map_probe("cfi_probe", s3c_nor_map); if (!s3c_nor_mtd) { /* 使用jedec协议探测NOR Flash */ 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; } add_mtd_partitions(s3c_nor_mtd, s3c_nor_parts, 2);
4.测试:
使用mtd-utils-05.07.23.tar.bz2