驱动开发学习

驱动

1、驱动开发环境

完成系统移植的三步:u-boot启动引导程序、内核镜像、文件系统,u-boot启动引导程序最好固化到开发板上,内核镜像通过tftp服务从ubuntu下载,文件系统通过nfs服务从ubuntu共享到开发板,开发板启动计数时按任意键进入u-boot命令模式设置bootcmd和bootargs

# setenv serverip 192.168.3.120
# setenv ipaddr 192.168.3.233
# setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;bootm 41000000 – 42000000\;
#setenv bootargs root=/dev/nfs\; nfsroot=192.168.3.120:/source/rootfs rw console=ttySAC2,115200  init=/linuxrc  ip=192.168.3.233
# saveenv

注意:192.168.3.120 对应Ubuntu的ip

192.168.3.233 对应板子的ip

这两个ip应该根据自己的实际情况适当修改

重启开发板进入自启动模式自动加载内核和文件系统

2、驱动开发工具使用source insight软件和vim工具,当然如果不想频繁地从Windows复制到ubuntu可以使用共享文档,这样就可以在Windows上写好代码在ubuntu中直接编译。

设置共享文档步骤:

  1. 打开VMware,选择虚拟机设置选择选项选择共享文件夹,点击总是启用,点击添加就可以设置共享文件夹了

  2. 设置完成后可以在ls /mnt/hgfs查看共享目录是否挂载

  3. 如果没有挂载可以通过vmware-hgfsclient查看共享文件夹

  4. 挂载共享文件夹命令vmhgfs-fuse .host:/my /mnt/hgfs其中my是查看共享文件夹时显示的名字,/mnt/hgfs是挂载路径,挂载路径必须是空的文件夹否则可能失败

source insight---查看和编写代码工具

  1. 将ubuntu中的linux内核代码复制到Windows中

  2. 在source insight中新建项目

    在第一个对话框中,第一个文本框(行编辑器),输入工程的名字

    在第二个对话框中,第一个本文框中选择刚解压的Linux内核源码目录(顶层linux-3.14),点击ok

    在第三个对话框中,在对话框中选择要查看的目录/文件 需要选择的目录文件: include init kernel arch/arm/kernel arch/arm/include/asm driver/base driver/char driver/i2c driver/spi fs/char_dev.c 点击close关闭

    重新选择project---->open project ​ 选择刚才创建的工程名 ​ ok ​ 如果提示同步,则选择确认进行同步

  3. 开始编写驱动代码

    驱动代码必须包含四部分:

    a.头文件

    #include 
    #include 

    b.加载和卸载时的函数定义

    static int __init hello_init(void)
    {
        return 0;
    }
    ​
    static void __exit hello_exit(void)
    {
    ​
    }

    c.加载和卸载的入口声明

    module_init(hello_init);//当使用insmod 驱动名称.ko 加载驱动时执行函数hello_init()
    module_exit(hello_exit);//当使用remod 驱动名称 卸载驱动时执行函数hello_exit()

    d.协议选择GPL

    MODULE_LICENSE("GPL");

    3、驱动操作

    a.加载驱动使用命令

    insmod 驱动程序路径.ko

    b.加载好驱动后可以通过命令查看驱动

    lsmod

    c.卸载驱动命令(不用加.ko)

    rmmod 驱动程序名

    字符设备驱动创建框架

    1、申请设备号

    int register_chrdev(unsigned int major,const char *name,const struct file_operations *fops)
    参数1:unsigned int major------大于0则是申请对应的主设备号;等于0则是由内核分配主设备号
    参数2:const char *name------是注册时的名字
    参数3:const struct file_operations *fops------是用来关联文件IO接口的结构体
    返回值:当参数1大于0时,正确返回0,失败返回负数
           当参数1等于0时,正确返回主设备号,失败返回负数

    2、创建设备节点(生成对应的驱动文件)

    1. 创建文件信息结构体

      struct class * class_create(owner,name);
      参数1:owner-----拥有者,一般THIS_MODULE
      参数2:name-----字符串,描述信息
      返回值:struct class *------信息结构体       
    2. 创建字符驱动设备文件(节点),一般默认建在/dev下,但是可以在任意目录下创建

      struct device *device_create(   struct class *class,struct device *parent,dev_t devt,void *drvdata,const char *fmt, ...)
      参数1:struct class *class--------class结构体,创建的设备文件的信息内容,通过 class_create()函数创建
      参数2:struct device *parent--------表示父类对象,一般直接写NULL,结构体地址
      参数3:dev_t devt--------设备号可以有函数MKDEV(ma, mi)获得,ma------主设备号, mi------次设备号,也可以由主设备号左移20位或上一个数字得到这个数字不能大于2^20,例:major<<20|0
      参数4:void *drvdata-------私有数据,一般填NULL
      参数5:const char *fmt, ...--------设备文件名字符串首地址
      返回值:struct device *---------设备节点对象(设备文件描述),成功返回地址,失败返回NULL
    3. 文件IO接口层实现,应用程序调用文件io时,驱动程序也调用对应的文件io接口函数 在结构体 struct file_operations 每一个成员变量都代表绑定一个系统调用(文件io)函数,只要对结构体中的成员赋值,就代表值绑定上一个文件io函数

      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 (*iterate) (struct file *, struct dir_context *);
      		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 *, loff_t, loff_t, 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);
      		int (*show_fdinfo)(struct seq_file *m, struct file *f);
      	};//函数指针的集合,
    4. 驱动控制硬件,控制外设,其实就是控制地址,通过地址往寄存器写入、读出控制 内核驱动是通过虚拟地址操作 初始化硬件

      地址映射:
      void * ioremap(cookie,size);
      参数1:cookie-----物理地址
      参数2:size-----映射内容大小,字节
      返回值:返回映射成功后的虚拟内存地址
      操作虚拟内存地址中的内容就是操作对应的物理地址空间内容

    字符设备驱动卸载时需要的函数

    在卸载入口中实现,清除 与初始化逆序过程进行卸载

    //1、映射释放(中断释放)
    iounmap(映射的虚拟内存地址);----释放映射地址
    //2、释放设备文件
    void device_destroy(struct class * class,dev_t devt);
    //3、释放设备文件结构体
    void class_destroy(struct class * cls)
    //4、释放设备号
    void unregister_chrdev(unsigned int major,const char * name)

    字符驱动模型举例:

    通过字符设备驱动控制一颗LED灯

    //头文件包含
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    //使用结构体表示一个驱动对象(面向对象的编程思想)
    struct Led_Dev
    {
    unsigned int *gpx1con;
    unsigned int *gpx1dat;
    struct class * class;
    struct device * dev;
    unsigned int major;
    unsigned int dev_no;
    };
    struct Led_Dev led;
    //4、文件IO功能与设备功能实现绑定
    ssize_t led_read (struct file * flie, char __user * data, size_t size, loff_t * ops)
    {
            return 0;
    }
    ssize_t led_write (struct file * file, const char __user * data, size_t size, loff_t * ops)
    {
            //data是应用程序传递的数据的地址,size 传递的大小
            int num;
            copy_from_user(&num,data,size);//从应用程序获取数据存到num中
           
            if(num==1)
            {
                    *(led.gpx1dat) |= 1;
            }
            else
            {
                    *(led.gpx1dat) &= ~1;
            }
    
            return 0;
    }
    int led_open (struct inode * inode, struct file * file)
    {
            printk("open ok\n");
            return 0;
    }
    int led_close (struct inode * inode, struct file * file)
    {
            printk("close ok\n");
            return 0;
    }
    
    
    const struct file_operations fops=
    {
    	.open = led_open,
    	.release = led_close,
    	.write = led_write
    
    };
    
    //驱动加载与卸载函数实现
    static int __init led_init(void)
    {
    	led.major = 250;
    	led.dev_no = led.major<<20|0;
    //1、申请设备号
    	
    	
    	int res = register_chrdev(led.major,"led_dev",&fops);
    	if(res !=0 )
    	{
    		printk("register dev error\n");
    		goto err_1;
    		//return -1;
    	}
    //2、创建设备文件
    	led.class = class_create(THIS_MODULE,"led_cls");
    	if (IS_ERR(led.class))
    	{
    		printk("class create error\n");
    		goto err_2;
    		//unregister_chrdev(major,name);
    		//return -1;
    	}		
    	led.dev = device_create(led.class, NULL, led.dev_no,NULL,"led");
    	if(IS_ERR(led.dev))
    	{
    		printk("device create error\n");
    		goto err_3;
    		//class_destroy(class);
    		//unregister_chrdev(major,name);
    		//return -1;
    	}
    //3、驱动设备控制硬件
    	//硬件寄存器地址映射
    	led.gpx1con = ioremap(0x11000c20, 4);
    	if(led.gpx1con==NULL)
    	{
    		printk("gpx1con ioremap error\n");
    		goto err_4;
    		//device_destroy(class, dev_no);
    		//class_destroy(class);
    		//unregister_chrdev(major,name);
    		//return -1;
    	}
    	led.gpx1dat = ioremap(0x11000c24, 4);
    	if(led.gpx1dat==NULL)
    	{
    		printk("gpx1dat ioremap error\n");
    		goto err_5;
    		//iounmap(gpx1con);
    		//device_destroy(class, dev_no);
    		//class_destroy(class);
    		//unregister_chrdev(major,name);
    		//return -1;
    	}
    	//硬件初始化
    	*(led.gpx1con) = *(led.gpx1con) & ~0xf | 1;
    	*(led.gpx1dat) |= 1;
    	return 0;
     //出错处理的代码   
    err_5:
    	//1、映射地址释放
    	iounmap(led.gpx1con);
    	
    err_4:	//2、设备文件释放
    	device_destroy(led.class,led.dev_no);//释放设备文件
    
    err_3:
    	class_destroy(led.class);//释放文件信息结构体
    	
    err_2:	//3、驱动设备号注销
    	unregister_chrdev(led.major,"led_dev");
    	
    err_1:
    	return -1;
    }
    
    
    static void __exit led_exit(void)
    {
    	//卸载驱动,与初始化逆序
    	//1、映射地址释放
    	iounmap(led.gpx1con);
    	iounmap(led.gpx1dat);
    	//2、设备文件释放
    		
    	device_destroy(led.class, led.dev_no);//释放设备文件
    	class_destroy(led.class);//释放文件信息结构体
    
    	//3、驱动设备号注销
    	
    	unregister_chrdev(led.major,"led_dev");
    	
    }
    
    //驱动加载与卸载
    module_init(led_init);
    module_exit(led_exit);
    
    //协议包含GPL
    MODULE_LICENSE("GPL");

    驱动中实现中断

    中断驱动---检测外部中断 获取外设的数据内容,通过中断信号进行获取 在驱动中设置外设为中断模式:当外设产生设定的特定信号(就是中断) 在驱动中实现中断处理操作(函数)

    本文以按键中断为例

    需要使用按键设备,需要先在设备树中说明使用的按键是一个中断设备,我使用的板子是Samsung的Exynos系列,按键使用的是KEY3

    驱动开发学习_第1张图片

     

开发板管脚

 

查看数据手册得到GPX1_2使用的中断是XEINT_10

 

 

在设备树中:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi

	gpx1: gpx1 {
                    		gpio-controller;
                    		#gpio-cells = <2>;

                   		interrupt-controller;
                    		interrupt-parent = <&gic>;
                    		interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
                             		    	<0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
                    		#interrupt-cells = <2>;
           	 	};

	在设备树中添加自己的硬件设备信息---添加key3节点-----描述当前设备的的信息内容(中断号)
	arch/arm/boot/dts/exynos4412-fs4412.dts:实现硬件描述(中断号)
	
	key3_node {
                    		compatible = "key3";
                    		interrupt-parent = <&gpx1>;
                   		interrupts = <2 4>;//26
    		};

在驱动中申请中断,实现中断处理

a、获取到中断号
	获取设备树节点,返回值就是从设备树中找到的节点
	struct device_node *of_find_node_by_path(const char *path);

	从节点中获取到中断号,返回值就是中断号
	unsigned int irq_of_parse_and_map(struct device_node *dev,int index);

b、申请中断
	int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
	参数1:
		unsigned int irq:申请中断的中断号

	参数2:
		irqreturn_t (*)(int, void *)    ----   irq_handler_t
		irq_handler_t handler:函数指针,进行注册中断,当产生中断时调用对应的函数进行处理
	参数3:
		unsigned long flags:中断处理的触发方式
		#define IRQF_TRIGGER_NONE	0x00000000
		#define IRQF_TRIGGER_RISING	0x00000001
		#define IRQF_TRIGGER_FALLING	0x00000002
		#define IRQF_TRIGGER_HIGH	0x00000004
		#define IRQF_TRIGGER_LOW	0x00000008

	参数4:
		const char *name:字符串首地址,中断的描述信息
			/proc/inruppter
	参数5:
		void *dev:传递给参数2的函数进行自动调用的(作为参数2这个函数的参数)

	返回值:
		成功返回0,失败返回非0
	释放中断:
	void free_irq(unsigned int irq,void * dev_id)
	参数1:
		unsigned int irq
		中断号
	参数2:
		void * dev_id:与申请中断第五个参数保持一致

中断分上下两部分:

上部分处理时间短、不费时间的中断处理

下部分处理一些费时间的中断

下部分就是将耗时操作延后处理,一般在上半部分的处理函数中调用

1、softirq:软中断,处理级别比较高,在内核机制中,需要修改内核源码功能 ​ 2、tasklet:实际上就是内部调用了softirq ​ 3、workqueue:工作队列

驱动中申请中断举例:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 

char c;

struct key_desc{
	unsigned int major;
	struct class * cls;
	dev_t devno;
	struct device * dev;
	}key;

irqreturn_t key_irq_handler(int i, void * j)
{
	c='q';
	printk("irqno : %d;input char %c\n",i,c);

	return IRQ_HANDLED;
}


//3、文件IO接口功能关联
ssize_t key_read (struct file * file, char __user * data, size_t size, loff_t * ops)
{
	
	printk("key read\n");
	int n = copy_to_user(data, &c, 1);
	c='w';
	return 0;
}
int key_open (struct inode * inode , struct file * file)
{
	printk("key open\n");
	return 0;
}

int key_release (struct inode * inode , struct file *file)
{
	printk("key close\n");
	return 0;
}

const struct file_operations fops = {
	.read = key_read,
	.release = key_release,
	.open = key_open
};

static int __init keydev_init(void)
{
	key.major = 252;
	//1、申请设备号
	int res = register_chrdev( key.major,"key", &fops);
	if(res<0)
	{
		goto err1;	
		
	}
	//2、设备文件
	key.cls = class_create(THIS_MODULE, "cls");
	if(IS_ERR(key.cls))
	{
		goto err2;	
		
	}
	key.devno = key.major << 20 | 0;
	key.dev = device_create(key.cls, NULL,key.devno ,NULL,"key_dev");
	if(IS_ERR(key.dev))
	{
		goto err3;	
		
	}
	//4、硬件初始化
	//a.获取中断号
	struct device_node * node = of_find_node_by_path("/key3");//查找设备树中节点为key_3
	if(IS_ERR(node))
	{
		goto err4;	
		
	}
	int irqno = irq_of_parse_and_map(node, 0);//申请中断号
	if(irqno<0)
	{
		goto err5;
	}
	//b.申请中断
	res = request_irq(irqno, key_irq_handler,IRQF_TRIGGER_FALLING,"key_in", NULL);
	if(res<0)
	{
		goto err6;
	}
	return 0;
err6:
	irqno = -1;

err5:
	node=NULL;

err4:
	device_destroy(key.cls, key.devno);

err3:
	class_destroy(key.cls);

err2:
	unregister_chrdev(key.major, "key");

err1:
	return -1;
}

static void __exit key_exit(void)
{

	device_destroy(key.cls, key.devno);
	class_destroy(key.cls);
	unregister_chrdev(key.major, "key");
}

module_init(keydev_init);
module_exit(key_exit);

MODULE_LICENSE("GPL");






测试程序

#include 
#include 
#include 
#include 


int main()
{


	char buf;
	int num;
	int fd = open("/dev/key_dev",O_RDONLY);
	while(1)
	{
		
		num=0;
		num = read(fd,&buf,1);
		printf("%d%c\n",num,buf);
	}
	return 0;
}

Makefile

#编译驱动代码
KERNEL_PATH = /home/ubuntu/code/kernel/linux-3.14
#APP=beep_test#测试程序的名字不包括后缀
MODULES_PATH = $(shell pwd)
obj-m += key_int.o #把.c编译为.o文件注意.o前的名字与驱动文件名字一致

#编译为驱动程序.ko 要借助已经编译过的内核
all:
	make modules -C $(KERNEL_PATH) M=$(MODULES_PATH)
#	arm-none-linux-gnueabi-gcc $(APP).c -o $(APP)

install:
#	cp *.ko $(APP) /home/ubuntu/rootfs
	cp *.ko /home/ubuntu/rootfs

驱动中的阻塞IO与非阻塞IO实现

驱动中实现阻塞: 要创建等待队列头: wait_queue_head_t head; init_waitqueue_head(&head);

	1、在需要等待的位置(没有数据),就阻塞等待
		
		wait_event_interruptible(wq,condition)-----根据参数是否进行阻塞等待,完成阻塞等待

		参数1:
			wq:等待队列头,把当前进程加入到哪个等待队列中

		参数2:
			condition:是否执行阻塞等待的条件

			condition:真---不进行阻塞
			condition:假---进行阻塞

	2、合适位置进行阻塞唤醒
		wake_up_interruptible(&head);

非阻塞:在进行读写操作时,如果没有数据,就立即返回,如果有数据读取数据然后立即返回 应用程序:设置为非阻塞打开 int fd = open("/dev/key3",O_RDONLY | O_NONBLOCK);

	驱动文件中在阻塞前面添加:
	if((file->f_flags & O_NONBLOCK != 0) && (condition == 0))
		return -1;

驱动总线模型: 驱动框架: 0、声明实现入口函数(module_init、module_exit) 1、申请设备号(register_chrdev) 2、创建设备节点(class_create、device_create) 3、硬件初始化 ioremap地址映射 中断申请 4、实现文件IO接口

总线模型: 总线bus 驱动driver 设备device

总线bus: struct bus_type:总线对象,描述一条总线,管理device、driver,进行匹配

struct bus_type
{
	const char		*name;:总线名字
	int (*match)(struct device *dev, struct device_driver *drv);总线调用匹配设备和驱动,返回值就表示匹配成功与否
};

注册总线: ​ int bus_register(struct bus_type * bus); ​ 参数: ​ struct bus_type * bus:总线对象 ​ 注销总线: ​ void bus_unregister(struct bus_type * bus);

驱动driver: struct device_driver :驱动对象,描述一个驱动,对驱动进行说明

struct device_driver { ​ const char *name;:驱动的名字 ​ struct bus_type bus;总线对象,表示要把驱动注册到哪条总线 ​ int (probe) (struct device dev);如果匹配成功,则调用该驱动的probe函数,创建驱动(申请设备号。。。) ​ int (remove) (struct device *dev);当设备对象和驱动对象移除总线时会调用 ​ } ​ 注册驱动到总线: ​ int driver_register(struct device_driver * drv); ​ 从总线上注销: ​ void driver_unregister(struct device_driver * drv);

设备device: struct device { struct kobject kobj;//所有对象的父类 const char *init_name;设备名 struct bus_type *bus;总线对象,表示要把设备注册到哪条总线 void platform_data;自定义数据,指向任意类型,可以存储设备信息 void (release)(struct device *dev);设备对象从总线移除时会调用 }; 注册设备到总线: int device_register(struct device * dev); 从总线上注销: void device_unregister(struct device * dev);

你可能感兴趣的:(1024程序员节,c++)