2018-04-13 13:48:20 qq_27997595 阅读数 810
  • NandFlash和iNand-1.11.ARM裸机第十一部分

    本期课程主要讲述了2种常见的块存储设备:NandFlash和iNand。分别从物理接口、协议、芯片内部存储原理、SoC中的控制器、代码实践分析等几个方面详细讲述这两种存储设备。本课程的目标是让大家对块设备及其接口协议有个框架性的了解,给将来学习linux驱动时的块设备驱动打下基础。

    6883 人正在学习 去看看 朱有鹏

驱动分类

1   常规分类

1.1       字符设备:以字节为最小访问单位的设备,通常支持open,close,read,write系统调用。如串口、LED、按键

1.2       块设备:以块为最小访问单位的设备(块一般为512字节或512字节的倍数),linux中允许块设备传送任意字节数。如硬盘、flash、SD卡。

1.3       网络接口:负责发送和接收数据报文,可以是硬件设备,如网卡,也可以是纯软件设备,如回环接口(lo)

2   总线分类法

               1.1             USB设备

               1.2             PCI设备

               1.3             平台总线设备

学习方法

1   构建驱动模型

2   添加硬件操作

3   驱动测试

硬件访问

1   地址映射

1.1 静态映射

void *ioremap(physaddr,size)

physaddr:待映射的物理地址

size:映射的区域长度

返回值:映射后的虚拟地址

1.2 动态映射

用户事先指定映射关系,在内核启动时自动将物理地址映射为虚拟地址。

struct map_desc

{

     unsigned long virtual;/映射后的虚拟地址

     unsigned long pfn;/物理地址所在的页帧号

     unsigned long length;/映射长度

     unsigned int type;/映射的设备类型

}

2   寄存器读写

unsigned ioread8(void *addr)

unsigned ioread16(void *addr)

unsigned readb(address)

unsigned readw(address)

unsigned readl(address)

 

void iowrite8(u8 value,void *addr)

void iowrite16(u16 value,void *addr)

void iowrite32(u32 value,void *addr)

void writeb(unsigned value, address)

void writew(unsigned value, address)

void writel(unsigned value, address)

驱动的运用

1   编译安装驱动

1.1 创建Makefile

obj-m := memdev.o

KDIR := /home/…/linux-tq2440

all:

make–C $( KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm

1.2 拷贝驱动(内核模块)到/…/rootfs

1.3 安装驱动程序

insmod  memdev.ko

1.4 查看 lsmod

2   字符设备文件

2.1       查看主设备号

cat /proc/devices

主设备号   设备名

2.2       创建设备文件

mknod /dev/文件名 c 设备号 次设备号(0~255)

3   应用程序

3.1 打开字符设备文件

fd = open(“/dev/memdev0”,O_RDWR);

3.2 操作字符设备文件

write(fd,&buf,sizeof(int));

read(fd,&buf,sizeof(int));

3.3 关闭字符设备文件

close(fd);

3.4 编译(静态编译)

arm-linux-gcc –static 文件名.c –O文件名

字符设备驱动模型

1    设备描述结构cdev

1.1 结构定义

struct cdev

{

struct kobject kobl;

struct module *owner;

const struct file operations *ops ;//设备操作集

struct lis thead list;

dev_t dev;//设备号。

unsigned int count;设备数

}

1.2 设备号类型dev_t

1.2.1 dev_t介绍:实质为32位的unsigned int,高十二位为主设备号,低二十位为次设备号。

1.2.2 操作设备号:

dev_t dev=MKDEV(主设备号,次设备号)

主设备号=MAJOR(dev_t dev)

次设备号=MINOR(dev_t dev)

1.2.3 申请设备号:

静态申请:intregister_chrdev_region(dev_t first, unsigned int count, char *name);

first: 待分配的起始设备编号,通常为0;

count: 连续设备编号的总数

name:设备名(cat /proc/devices

动态申请:intalloc_chrdev_region(dev_t *dev,unsigned int -firstminor,unsigned int -count,char*name)

dev:得到的设备号保存位置

-firstminor: 待分配的起始设备编号,通常为0;

-count: 连续设备编号的总数

name: 设备名(cat /proc/devices

1.2.4 注销设备号:unregister_chardev_region

1.3 操作集struct file operations

1.3.1 structfile operations介绍:函数指针的集合,定义能在设备上进行的操作。对于不支持的操作设置为NULL

1.3.2 例:struct file operations dev_fops =

{

.llseek  = NULL;

.read= dev_read,

.write= dev_write,

.ioctl= dev_inctl,

.open= dev_open,

.release= dev_release,

};

2    驱动初始化

2.1 分配设备描述结构

    静态分配:structcdev mdev;

动态分配:structcdev *pdev = cdev_alloc();

2.2 初始化设备描述结构

cdev_init(struct cdev*cdev,const struct file_operations *fops)

*cdev:待初始化的cdev结构/*fops:设备对应的操作函数集

2.3 注册设备描述结构

    cdev_add(struct cdev *p,dev_t dev,unsignedcount)

p:待添加到内核的字符设备结构

dev:设备号

count:该类设备的设备个数

2.4 硬件初始化

3         设备操作(设备方法)

3.1 int  (*open) (struct inode *,struct file *)  打开设备,响应open系统

3.1.1 structfile:

3.1.1.1   linux系统中,每个打开的文件,都会关联一个structfile,由内核在文件打开时创建,文件关闭后释放

3.1.1.2   重要成员

loff_t f_pos   //文件读写指针

struct file_operation*f_op  //文件对应的操作

3.1.2 structinode

3.1.2.1   每个存在于文件系统的文件都会关联一个inode结构,记录文件物理信息。

3.1.2.2   重要成员:

dev_t i_rdev   //设备号

3.2 int  (*release) (struct inode *,struct file*)  关闭设备,响应close系统

3.3 loff_t  (*llseek) (struct file *,loff_t,int)  重定位读写指针,响应lseek系统调用

3.4 ssize_t(*read) (struct file *,char _user *,size_t,loff_t *)  从设备读取数据,响应read系统调用

3.4.1 参数分析:

filp:与字符设备文件关联的file结构指针,由内核创建

buff:从设备读到的数据需要保存的位置,由read系统调用提供

count:请求传输的数据量,由read系统调用提供

offp:文件读写位置,由内核从file结构中取出传递进来

3.4.2 从设备中读取数据(硬件访问类操作)

3.4.3 将读到的数据返回给应用程序

3.5 ssize_t(*write) (struct file *,const char _user *,size_t,loff_t *)  向设备写入数据,响应write系统调用

3.5.1 参数分析:

filp:与字符设备文件关联的file结构指针,由内核创建

buff:从设备读到的数据需要保存的位置,由read系统调用提供

count:请求传输的数据量,由read系统调用提供

offp:文件读写位置,由内核从file结构中取出传递进来

3.5.2 从应用程序提供的地址中取出数据

3.5.3 将数据写入设备(硬件访问类操作)

4         驱动注销

4.1       用cdev_del函数卸载驱动程序

2017-03-25 21:54:37 u013377887 阅读数 666
  • NandFlash和iNand-1.11.ARM裸机第十一部分

    本期课程主要讲述了2种常见的块存储设备:NandFlash和iNand。分别从物理接口、协议、芯片内部存储原理、SoC中的控制器、代码实践分析等几个方面详细讲述这两种存储设备。本课程的目标是让大家对块设备及其接口协议有个框架性的了解,给将来学习linux驱动时的块设备驱动打下基础。

    6883 人正在学习 去看看 朱有鹏

前言

上一节介绍了 linux 驱动开发中 module 的导出模块,在这一节中首先 linux 设备驱动开发中相关的概念,设备驱动的分类和学习过程中的重点和难点。

正文

计算机系统的硬件主要由 CPU 、存储器和外设组成,目前 CPU 内部往往集成了存储器和外设适配器,比如 ARM 芯片中继承的 UART 、I2C控制器、USB控制器、SDRAM 控制器以及片内的 RAM 和Flash。

驱动针对的对象是存储器和外设(包括 CPU 内部集成的存储器和外设) 而不是针对 CPU 核的。

Linux 将存储器和外设分为三大类:

  • 字符设备
  • 块设备、
  • 网络设备

字符设备是指那些必须以 串行 顺序依次进行访问的设备。比如:* 触摸屏、鼠标*
块设备可以任意顺序进行访问,以块为单位进行操作,比如硬盘等。
字符设备和块设备之前并没有明显的界限,Flash设备,符合块设备的特点,但是我们仍然可以把它作为一个字符设备。

另外一个设备分来方法中所称的 I2C驱动、USB驱动、PCI 驱动、LCD驱动等本身可纳入3个基础大类,但是对于这些复杂的设备,linux也定义了独特的驱动体系结构。

linux设备驱动和整个软硬件系统的关系

除网络设备外,字符设备和块设备都被映射到 linux 系统的文件和目录中,通过文件目录接口 open() 、read()、write()、close() 等可以访问字符设备和块设备。

应用程序可以使用 Linux 的系统调用接口变成,也可以使用 C 库函数进行编程。

linux设备驱动和整个软件系统的关系

linux设备驱动的重点和难点

  • 编写 Linux 设备驱动程序需要很好的硬件基础,懂得 SRAM、FLASH、SDRAM、磁盘的读写方式、UART、 I2C、USB 等设备的接口以及轮询、中断、DMA 的原理、PCI 总线的工作方式、以及CPU的内存管理单元 (MMU)等。

  • 编写 Linux 设备驱动程序需要良好的 C 语言基础,能灵活的利用 C 语言的结构体、指针、函数指针、以及内存的动态申请和释放等。

  • 编写 Linux 设备驱动程序需要有一定的 linux 内核基础,至少明白驱动和内核的接口。

  • 编写 Linux 设备驱动程序需要工程师有非常好的多任务并发控制和同步的基础,因为在驱动中会大量使用自旋锁、互斥、信号量、等待队列等并发和同步机制。

总结

linux 字符设备驱动是最常见的驱动,学习字符设备驱动,要在字符设备驱动的基础上,加深对 I2C 、USB 等驱动框架的理解;同时重点理解和学习 Linux设备驱动的重点和难点部分的内容,提供自己的能力。

参考文献

设备驱动开发详解 第二版 宋宝华

2017-11-27 08:48:33 shunli008 阅读数 197
  • NandFlash和iNand-1.11.ARM裸机第十一部分

    本期课程主要讲述了2种常见的块存储设备:NandFlash和iNand。分别从物理接口、协议、芯片内部存储原理、SoC中的控制器、代码实践分析等几个方面详细讲述这两种存储设备。本课程的目标是让大家对块设备及其接口协议有个框架性的了解,给将来学习linux驱动时的块设备驱动打下基础。

    6883 人正在学习 去看看 朱有鹏

FS4412开发板_驱动开发->中断

  • *0
  • 1.添加设备树配置信息
  • 2.中断申请与释放
  • LaTex数学公式
  • UML序列图和流程图
  • 离线写博客
  • 导入导出Markdown文件
  • 丰富的快捷键

0.查找开发板上K2相关信息

0.1在外围板上查找k2
这里写图片描述
0.2在内核板上搜索

1.添加设备树配置信息

1.1修改内核文件/arch/arm/boot/dts/exynos4412-fs4412.dts
添加

//注意在大括号内添加
key2_node {
    compatible = "key2";  //用于匹配  名称
    interrupt-parent = <&gpx1>; //哪一组中断
    interrupts = <1 4>; //一组中的第几个   4表示中断的触发方式
};

1.2编译设备树、
注意在内核根目录下编译

linux@ubuntu:~/linux-3.14$ make dtbs
  DTC     arch/arm/boot/dts/exynos4412-fs4412.dtb

拷贝到tftp目录下

linux@ubuntu:~/linux-3.14$ cp arch/arm/boot/dts/exynos4412-fs4412.dtb /home/linux/tftp/

1.3将tftp到开发板上

  • 中断申请:
    request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)
    参数1: 中断号 -----》从设备树中获取
    参数2: 中断处理函数
            typedef irqreturn_t (*irq_handler_t)(int, void *);

    参数3: 中断触发方式
            高电平
            低电平
            上升沿
            下降沿

    参数4:向内核注册的中断名称   cat /proc/interrupts

    参数5:私有数据  

脚注

生成一个脚注1.

目录

[TOC]来生成目录:

数学公式

使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.

  • 行内公式,数学公式为:Γ(n)=(n1)!nN
  • 块级公式:

x=b±b24ac2a

更多LaTex语法请参考 这儿.

UML 图:

可以渲染序列图:

Created with Raphaël 2.1.0张三张三李四李四嘿,小四儿, 写博客了没?李四愣了一下,说:忙得吐血,哪有时间写。

或者流程图:

Created with Raphaël 2.1.0开始我的操作确认?结束yesno
  • 关于 序列图 语法,参考 这儿,
  • 关于 流程图 语法,参考 这儿.

离线写博客

即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。

用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。

博客发表后,本地缓存将被删除。 

用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。

注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱

浏览器兼容

  1. 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
  2. IE9以下不支持
  3. IE9,10,11存在以下问题
    1. 不支持离线功能
    2. IE9不支持文件导入导出
    3. IE10不支持拖拽文件导入


  1. 这里是 脚注内容.
2018-12-22 11:29:00 Archar_Saber 阅读数 84
  • NandFlash和iNand-1.11.ARM裸机第十一部分

    本期课程主要讲述了2种常见的块存储设备:NandFlash和iNand。分别从物理接口、协议、芯片内部存储原理、SoC中的控制器、代码实践分析等几个方面详细讲述这两种存储设备。本课程的目标是让大家对块设备及其接口协议有个框架性的了解,给将来学习linux驱动时的块设备驱动打下基础。

    6883 人正在学习 去看看 朱有鹏

1.11 字符设备驱动模型

在任何一种驱动模型中,设备都会用内核中的一种结构来描述。字符设备在内核中使用struct cdev来描述。

struct cdev{
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;//设备操作函数集
    struct list_head list;
    dev_t dev;//设备号
    unsigned int count;//设备数
};

字符设备驱动模型的整体框架:

                    

1.11.1 设备号

1,主设备号和次设备号

字符设备文件与字符驱动程序通过主设备号建立起对应关系;驱动程序通过次设备号区分同类型的设备。

2,分配设备号

(1)静态申请

开发者自己选择一个数字作为主设备号,然后通过函数register_chrdev_region向内核申请使用。缺点:如果申请使用的设备号已经被内核中的其他驱动使用了,则申请失败。

int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char*name)
//参数
//dev:存放分配的设备号
//baseminor:次设备号的起始值
//count:设备数
//name:设备名

(2)动态申请

使用alloc_chrdev_region由内核分配一个可用的主设备号。优点:因为内核知道哪些号已经被使用了,所以不会导致分配到已经被使用的号。

int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)
//参数:
//dev:设备号
//baseminor:次设备起始值
//count:设备数
//name:设备名

3,操作设备号

linux内核中使用dev_t类型来定义设备号,dev_t这种类型其实质为32位的unsigned int ,其中高12位为主设备号,低20位次设备号。

(1)由主设备号和次设备号组合成dev_t(类型)

dev_t dev=MKDEV(主设备号,次设备号)

(2)从dev_t中分解出主设备号

主设备号=MAJOR(dev_t dev)

(3)从dev_t中分解出次设备号

次设备号=MINOR(dev_t dev)

4,注销设备号

不论使用什么方法分配设备号,都应该在驱动退出时,使用unregister_chrdev_region函数释放这些设备号。

1.11.2 操作函数集

struct file_operations是一个函数指针的集合,定义能在设备上进行的操作。

struct file_operations{
    struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long,             
unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
	long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
};

结构中的函数指针指向驱动程序中的函数,这些函数实现一个针对设备的操作,对于不支持的操作则设置函数指针为NULL。例如

struct file_operation dev_fops={
    .llseek=NULL,
    .read=dev_read,
    .write=dev_write,
    .ioctl=dev_ioctl,
    .open=dev_open,
    .release=dev_release,
};

1.11.3 字符设备初始化

1,分配描述结构cdev

(1)静态分配

struct cdev mdev;//直接定义一个struct cdev类型的变量。

(2)动态分配

struct cdev *pdev=cdev_alloc();//使用cdev_alloc()动态分配

2,初始化描述结构 cdev

struct cdv的初始化使用cdev_init函数来完成。

cdev_init(struct cdev *cdev,const struct file_opertations *fops)
//参数:
//cdev:待初始化的cdev结构 ; fops:设备对应的操作函数集

3,注册描述结构cdev

字符设备的注册使用cdev_add函数来完成。

cdev_add(struct cdev*p,dev_t dev,unsigned count)
//参数:
//p:待添加到内核的字符设备结构
//dev:设备号
//count:该类设备的设备个数

 1.11.4 实现设备操作

struct file:在linux系统中,每一个打开的文件,在内核中都会关联一个struct file,它由内核在打开文件时创建,在文件关闭后释放。

loff_t f_pos;//文件读写指针
struct file_operation *f_op;//该文件所对应的操作

struct inode:每一个存在于文件系统里面的文件都会关联一个inode结构,该结构主要用来记录文件物理上的信息。因此,它和代表打开文件的file结构是不同的。一个文件没有被打开时不会关联file结构,但是却会关联一个inode结构。

dev_t i_rdev;//设备号

注意:(1)从应用程序提供的地址中取出数据;(2)将数据写入设备中;

要使用专门的函数:(1)int copy_from_user(void *to,const void __user *from,int n)

(2)int copy_to_user(void __user*to,const void *from,int n)

1.11.5 驱动注销

当我们从内核中卸载驱动程序时候,需要使用cdev_del函数来完成字符设备的注销

1.11.6 使用字符驱动程序

1,编译/安装字符设备

 在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码。因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块。

2, 字符设备文件

通过字符设备文件,应用程序可以使用相应的字符设备驱动程序来控制字符设备。创建字符设备文件的方法一般有两种:
(1)使用mknod命令

mknod /dev/文件名 c 主设备号 次设备号

(2)使用函数在驱动程序中创建

                                                   

1.11.7 内存操作驱动实例

    /**********************************/
   /*                                */
  /*            驱动程序             */
 /*                                */
/**********************************/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>


int dev1_registers[5];
int dev2_registers[5];

struct cdev cdev; 
dev_t devno;

/*文件打开函数*/
int mem_open(struct inode *inode, struct file *filp)
{
    
    /*获取次设备号*/
    int num = MINOR(inode->i_rdev);
    
    if (num==0)
        filp->private_data = dev1_registers;
    else if(num == 1)
        filp->private_data = dev2_registers;
    else
        return -ENODEV;  //无效的次设备号
    
    return 0; 
}

/*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
}

/*读函数*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  int *register_addr = filp->private_data; /*获取设备的寄存器基地址*/

  /*判断读位置是否有效*/
  if (p >= 5*sizeof(int))
    return 0;
  if (count > 5*sizeof(int) - p)
    count = 5*sizeof(int) - p;

  /*读数据到用户空间*/
  if (copy_to_user(buf, register_addr+p, count))
  {
    ret = -EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
  }

  return ret;
}

/*写函数*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  int *register_addr = filp->private_data; /*获取设备的寄存器地址*/
  
  /*分析和获取有效的写长度*/
  if (p >= 5*sizeof(int))
    return 0;
  if (count > 5*sizeof(int) - p)
    count = 5*sizeof(int) - p;
    
  /*从用户空间写入数据*/
  if (copy_from_user(register_addr + p, buf, count))
    ret = -EFAULT;
  else
  {
    *ppos += count;
    ret = count;
  }

  return ret;
}

/* seek文件定位函数 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{ 
    loff_t newpos;

    switch(whence) {
      case SEEK_SET: 
        newpos = offset;
        break;

      case SEEK_CUR: 
        newpos = filp->f_pos + offset;
        break;

      case SEEK_END: 
        newpos = 5*sizeof(int)-1 + offset;
        break;

      default: 
        return -EINVAL;
    }
    if ((newpos<0) || (newpos>5*sizeof(int)))
    	return -EINVAL;
    	
    filp->f_pos = newpos;
    return newpos;

}

/*文件操作结构体*/
static const struct file_operations mem_fops =
{
  .llseek = mem_llseek,
  .read = mem_read,
  .write = mem_write,
  .open = mem_open,
  .release = mem_release,
};

/*设备驱动模块加载函数*/
static int memdev_init(void)
{
  /*初始化cdev结构*/
  cdev_init(&cdev, &mem_fops);
  
  /* 注册字符设备 */
  alloc_chrdev_region(&devno, 0, 2, "memdev");
  cdev_add(&cdev, devno, 2);
}

/*模块卸载函数*/
static void memdev_exit(void)
{
  cdev_del(&cdev);   /*注销设备*/
  unregister_chrdev_region(devno, 2); /*释放设备号*/
}

MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);


    /**********************************/
   /*                                */
  /*            应用程序             */
 /*                                */
/**********************************/
//write.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	int fd = 0;
	int src = 2013;
	
	/*打开设备文件*/
	fd = open("/dev/memdev0",O_RDWR);
	
	/*写入数据*/
	write(fd, &src, sizeof(int));
	
	/*关闭设备*/
	close(fd);
	
	return 0;	

}

//read.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	int fd = 0;
	int dst = 0;
	
	/*打开设备文件*/
	fd = open("/dev/memdev0",O_RDWR);
	
	/*写入数据*/
	read(fd, &dst, sizeof(int));
	
	printf("dst is %d\n",dst);
	
	/*关闭设备*/
	close(fd);
	
	return 0;	

}


 

 

 

 

2013-03-05 00:33:47 pkpkpkcom 阅读数 552
  • NandFlash和iNand-1.11.ARM裸机第十一部分

    本期课程主要讲述了2种常见的块存储设备:NandFlash和iNand。分别从物理接口、协议、芯片内部存储原理、SoC中的控制器、代码实践分析等几个方面详细讲述这两种存储设备。本课程的目标是让大家对块设备及其接口协议有个框架性的了解,给将来学习linux驱动时的块设备驱动打下基础。

    6883 人正在学习 去看看 朱有鹏

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.cmtdpart.c),另一部分是各个特定的FLASH的数据,例如分区。

3. MTD设备层:基于MTD原始设备,LINUX系统可以定义出MTD的块设备(主设备号31www.linuxidc.com和字符设备(设备号90),构成设备层。MTD字符设备在mtdchar.c实现,MTD块设备在mtdblock.c实现。

4. 设备节点:通过mknod/dev子目录下建立MTD字符设备节点(主设备号为90)和块设备节点(主设备号为31),用户通过访问此设备节点即可访问MTD字符设备和块设备。

从上图可以看出,MTD设备层与原始设备层打交道。通过分析源代码我们可以知道当上层要求对FLASH进行读写时,它会像设备层发出请求,设备层的读写函数会调用原始设备层中的读写函数,即mtd_info结构体(mtd原始设备层中描述设备的专用结构体)中的读写函数,而mtd_info中的函数会调用nand_chipnand硬件驱动层中描述设备的结构体,其中包含了针对特定设备的基本参数和设备操作函数)中的读写函数。

所以当我们写一个flash硬件驱动程序时,有以下步骤:

1. 如果FLASH要分区,则定义mtd_partition数组,将FLASH分区信息记录其中。

2. 在模块加载时为每一个chip(主分区)分配mtd_infonand_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_infomtd_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,读写NANDOOB,获取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内核中s3c2410nand 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;

}

博文 来自: u013488347
没有更多推荐了,返回首页