[Linux_IMX6ULL驱动开发]-基础驱动

这篇具有很好参考价值的文章主要介绍了[Linux_IMX6ULL驱动开发]-基础驱动。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 驱动的含义

如何理解嵌入式的驱动呢,我个人认为,驱动就是嵌入式上层应用操控底层硬件的桥梁。因为上层应用是在用户态,是无法直接操控底层的硬件的。我们需要利用系统调用(open、read、write等),进入内核态,通过打开对应的设备节点,通过read、write等通过编写的驱动函数来操控设备节点。

imx linux驱动开发,IMX6ULL,驱动开发,linux,imx6ull,驱动,嵌入式驱动

如何编写驱动

总的来说,驱动编写的大体步骤如下所示:

1、确定驱动的主设备号

2、定义自己的file_operation结构体,这个结构体的成员包含了很多的函数指针

3、我们需要在驱动文件中实现对应的函数,传入结构体中

4、编写一个驱动入口函数(对应的,也需要一个驱动卸载函数)

5、在驱动入口函数中,把file_operation结构体注册到内核当中、创建节点(class)、创建设备(相应的在驱动卸载函数中定义结构体从内核中卸载、节点、设备的卸载方法)

6、使用如下两个宏分别修饰入口函数和出口函数

7、使用 MODULE_LICENSE("GPL"); 遵守GPL协议,否则无法使用

基于如上步骤,我们进行以下操作

首先,我们需要三个文件,一个作为底层驱动文件,一个是上层APP文件,一个是Makefile

驱动文件 hello_driver.c
上层应用文件 hello_drv.c
Makefile

刚开始我们可能不知道到底要包含什么头文件,我们可以学习Linux内核中的文件来进行参考,我们可以打开 Linux-4.9.88\drivers\char\misc.c ,把里面的头文件拷贝过来使用。

首先我们需要定义一个全局变量作为驱动的设备号,然后定义一个file_operation结构体。需要注意,这两个变量都是全局变量,因为需要被多个函数使用。

file_operation结构体需要多个函数指针成员,在这里,我们定义四个函数,把函数指针赋值给结构体成员

其中需要注意的是,驱动和上层直接读写是需要通过两个函数来进行的,分别是 copy_to_user 和  copy_from_user,前者用于驱动中读的驱动函数,后者用于驱动中写的函数

imx linux驱动开发,IMX6ULL,驱动开发,linux,imx6ull,驱动,嵌入式驱动

同时,结构体成员函数的形参,返回值必须严格遵守一样的原则,否则会报错

imx linux驱动开发,IMX6ULL,驱动开发,linux,imx6ull,驱动,嵌入式驱动

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/* 确定主设备号 */
static int major = 0;
/* 缓存数组 */
static char kernal_buf[1024];


/* 数据超过1024,限制为1024 */
#define data_num(a,b) ( (a) < (b) ? (a) : (b) )



/* 定义函数入口地址 */
static int hello_drv_open (struct inode *node, struct file *file)
{
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
	return 0;
}


static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int return_size;
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
	return_size = copy_to_user(buf, kernal_buf, data_num(1024,size));
	return return_size;
	
}
static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int return_size;
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
	return_size = copy_from_user(kernal_buf, buf, data_num(1024,size));
	return return_size;
	
}
static int hello_drv_rease (struct inode *node, struct file *file)
{
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
	return 0;
}


/* 
	定义文件结构体
	读,写,打开,卸载
*/
static struct file_operations hello_driver = {
	.owner = THIS_MODULE,
	.open = hello_drv_open,
	.read = hello_drv_read,
	.write = hello_drv_write,
	.release = hello_drv_rease,
};

当我们为file_operation结构体的成员指定了对应的函数指针后,我们需要指定一个入口函数以及一个出口函数,并且在入口函数中,把file_operation注册到内核、节点的创建和设备的创建,在出口函数中完成上述三个的卸载(节点需要另外创建一个全局变量,struct class类型)

/* 节点的定义 全局变量 */
static struct class *hello_class;


/* 入口函数 */
static int __init hello_init(void)
{
	int err;
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);

	/* 注册结构体到内核后,返回主设备号 */
	major = register_chrdev(0, "hello", &hello_driver);
	//创建节点 /dev/hello
	hello_class = class_create(THIS_MODULE, "hello_class");
	err = PTR_ERR(hello_class);
	if (IS_ERR(hello_class))
	{
	
		printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
		/* 创建失败的话摧毁内核中的hello结构体 */
		unregister_chrdev( major, "hello");
		return -1;
	}
	/* 创建了节点后,需要创建设备 */	
    device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); 

	return 1;
}


/* 出口函数 */
static void __exit hello_exit(void)
{
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);

	/* 把device卸载 */
	device_destroy(hello_class, MKDEV(major, 0));
	/* 把class卸载 */
	class_destroy(hello_class);

	/* 把file_operation从内核中卸载 */
	unregister_chrdev( major, "hello");

}

当写好了入口函数和出口函数后,还需通过两个宏声明,否则系统不知道这两个函数分别是入口函数和出口函数

/* 需要用某些宏表示上述两个函数分别是入口函数和出口函数 */
module_init(hello_init);
module_exit(hello_exit);

这就是一个驱动的具体框架了,整体完整代码如下

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>



/*  
	流程
	1.file_operation结构体,实现内部对应函数
	2.注册结构体到内核,同时使用宏声明入口和出口函数,指引进入
	3.创建节点,让上层应用函数可以打开 /dev/...,节点class创建完毕后,创建device
		class提供了一种更高层次的设备抽象,而device则代表了具体的硬件设备

*/


/* 确定主设备号 */
static int major = 0;
/* 缓存数组 */
static char kernal_buf[1024];
/* 节点的定义 */
static struct class *hello_class;



/* 读多少的宏定义 */
#define data_num(a,b) ( (a) < (b) ? (a) : (b) )





/* 定义函数入口地址 */
static int hello_drv_open (struct inode *node, struct file *file)
{
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
	return 0;
}


static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int return_size;
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
	return_size = copy_to_user(buf, kernal_buf, data_num(1024,size));
	return return_size;
	
}
static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int return_size;
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
	return_size = copy_from_user(kernal_buf, buf, data_num(1024,size));
	return return_size;
	
}
static int hello_drv_rease (struct inode *node, struct file *file)
{
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
	return 0;
}


/* 
	定义文件结构体
	读,写,打开,卸载
*/
static struct file_operations hello_driver = {
	.owner = THIS_MODULE,
	.open = hello_drv_open,
	.read = hello_drv_read,
	.write = hello_drv_write,
	.release = hello_drv_rease,
};




/* 
	把结构体注册到内核
	为了能够把该结构体注册到内核
	需要init函数
*/
static int __init hello_init(void)
{
	int err;
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);

	/* 注册结构体到内核后,返回主设备号 */
	major = register_chrdev(0, "hello", &hello_driver);
	//创建节点 /dev/hello
	hello_class = class_create(THIS_MODULE, "hello_class");
	err = PTR_ERR(hello_class);
	if (IS_ERR(hello_class))
	{
	
		printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);
		/* 创建失败的话摧毁内核中的hello结构体 */
		unregister_chrdev( major, "hello");
		return -1;
	}
	/* 创建了节点后,需要创建设备 */	
    device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); 

	return 1;
}


/* 有注册函数就有卸载函数 */
static void __exit hello_exit(void)
{
	printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);

	/* 把device卸载 */
	device_destroy(hello_class, MKDEV(major, 0));
	/* 把class卸载 */
	class_destroy(hello_class);

	/* 把file_operation从内核中卸载 */
	unregister_chrdev( major, "hello");

}

/* 需要用某些宏表示上述两个函数分别是入口函数和出口函数 */
module_init(hello_init);
module_exit(hello_exit);

/* 遵循GPL协议 */
MODULE_LICENSE("GPL");



如上,驱动程序hello_driver.c就完成了 ,在这里我们通过上层应用来打开驱动节点,然后往里面写入数据,然后在从里面读取数据。应用的代码如下


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./hello_drv_test -w abc
 * ./hello_drv_test -r
 */
int main(int argc, char **argv)
{
	int fd;
	char buf[1024];
	int len;
	
	/* 1. 判断参数 */
	if (argc < 2) 
	{
		printf("Usage: %s -w <string>\n", argv[0]);
		printf("       %s -r\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open("/dev/hello", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return -1;
	}

	/* 3. 写文件或读文件 */
	if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
	{
		len = strlen(argv[2]) + 1;
		len = len < 1024 ? len : 1024;
		write(fd, argv[2], len);
	}
	else
	{
		len = read(fd, buf, 1024);		
		buf[1023] = '\0';
		printf("APP read : %s\n", buf);
	}
	
	close(fd);
	
	return 0;
}



 同时,我们还需要编写Makefile,Makefile和具体的解析如下所示

1、KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

        定义了KERN_DIR变量,指向了内核的目录

2、all:

        标记了Makefile的第一个目标,执行make的时候执行

3、make -C $(KERN_DIR) M=`pwd` modules 

        -C $(KERN_DIR):这是make的一个选项,用于改变到另一个目录并读取那里的Makefile。这告诉make工具首先进入这个目录,并在那里查找Makefile。

        M=`pwd` modules:M的意思是指定模块源代码的的位置,当指定了module作为目标后,就是告诉系统想要构建内核模块。内核构建系统会查找当前目录(由M变量指定)中的模块源代码,并生成相应的模块文件(通常是.ko文件)。

4、$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c 

        CROSS_COMPILE是环境变量,这列的意思是使用交叉编译器编译hello_drv_test.c 生成hello_drv_test.o。如果不存在交叉编译器会使用gcc

5、obj-m    += hello_driver.o

        这行告诉内核构建系统hello_driver.o是一个要构建的对象文件(即内核模块)


KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f hello_drv_test

obj-m	+= hello_driver.o

驱动的安装、卸载和现象

当我们在服务器上面编译完成后,会生成如下几个文件

imx linux驱动开发,IMX6ULL,驱动开发,linux,imx6ull,驱动,嵌入式驱动

我们通过挂载,把这两个文件挂载到开发板上

当前挂载的目录下存在 hello_driver.ko  hello_drv_test这两个文件。

首先,我们需要安装驱动,使用 insmod + 驱动名 ,来安装驱动

(lsmod也可以查看安装的驱动程序)

imx linux驱动开发,IMX6ULL,驱动开发,linux,imx6ull,驱动,嵌入式驱动

如上图,驱动程序成功的安装了

在这里我们使用应用文件写入驱动程序,再从中读出

imx linux驱动开发,IMX6ULL,驱动开发,linux,imx6ull,驱动,嵌入式驱动

当我们不使用驱动的时候,使用 rmmod+驱动名 卸载

imx linux驱动开发,IMX6ULL,驱动开发,linux,imx6ull,驱动,嵌入式驱动文章来源地址https://www.toymoban.com/news/detail-845908.html

到了这里,关于[Linux_IMX6ULL驱动开发]-基础驱动的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【IMX6ULL驱动开发学习】12.Linux SPI驱动实战:DAC驱动设计流程

    基础回顾: 【IMX6ULL驱动开发学习】10.Linux I2C驱动实战:AT24C02驱动设计流程_阿龙还在写代码的博客-CSDN博客 【IMX6ULL驱动开发学习】11.Linux之SPI驱动_阿龙还在写代码的博客-CSDN博客 查看芯片手册,有两种DAC数据格式,12位和16位,这里选用16位数据(2字节)编写驱动。  重点在

    2024年02月11日
    浏览(37)
  • 【IMX6ULL驱动开发学习】14.Linux驱动开发 - GPIO中断(设备树 + GPIO子系统)

    代码自取 【14.key_tree_pinctrl_gpios_interrupt】: https://gitee.com/chenshao777/imx6-ull_-drivers 主要接口函数: 1. of_gpio_count (获得GPIO的数量) 2. kzalloc (向内核申请空间) 3. of_get_gpio (获取GPIO子系统标号) 4. gpio_to_irq (根据GPIO子系统标号得到软件中断号) 5. request_irq (根据软件中断号

    2024年02月12日
    浏览(35)
  • 嵌入式linux之iMX6ULL驱动开发 | 移远4G模块EC800驱动移植指南

    回顾下移远4G模块移植过程, 还是蛮简单的。一通百通,无论是其他4G模块都是一样的。这里记录下过程,分享给有需要的人。环境使用正点原子的imax6ul开发板,板子默认支持中兴和移远EC20的驱动,这里要移植使用的是移远4G模块EC800。 imax6ul开发板 虚拟机(Ubuntu18.04) 交叉编译

    2024年02月12日
    浏览(42)
  • Linux下的IMX6ULL——开发板的第一个APP和驱动实验(三)

    前言: 万事开头难,如果我们在开发板上开发出第一个应用程序,第一个驱动程序,那么后续的开发就会稍微简单点,下面让我们来进行第一个应用程序和第一驱动程序的开发吧。 目录 一、开发板的第1个APP实验 1.通过Git仓库 2.通过windows上传  二、开发板的第1个驱动实验

    2024年02月08日
    浏览(29)
  • 【IMX6ULL驱动开发学习】09.Linux驱动之GPIO中断(附SR501人体红外感应驱动代码)

    Linux驱动的GPIO中断编程主要有以下几个步骤: 1、 通过GPIO号获取 软件中断号 (中断编程不需要设置GPIO输入输出,当然申请GPIO,设置输入也没问题) 参数 含义 gpio GPIO引脚编号 2、 注册 中断处理函数 ,设置中断 触发方式 (上升沿、下降沿等) 参数 含义 irq 软件中断号(通过

    2024年02月11日
    浏览(37)
  • 【IMX6ULL驱动开发学习】21.Linux驱动之PWM子系统(以SG90舵机为例)

    首先在 imx6ull.dtsi 文件中已经帮我们定义好了一些pwm的设备树节点,这里以pwm2为例 我们要在设备树(.dts)文件中引用和使能该节点,同时指定好pwm映射到的GPIO引脚(即pinctrl子系统,我这里映射到了GPIO1_9上) 使用pwm 只需要在设备树节点中添加两条属性信息,如下所示 pwms :属

    2024年02月12日
    浏览(99)
  • 【IMX6ULL驱动开发学习】10.Linux I2C驱动实战:AT24C02驱动设计流程

    前情回顾:【IMX6ULL驱动开发学习】09.Linux之I2C框架简介和驱动程序模板_阿龙还在写代码的博客-CSDN博客 目录 一、修改设备树(设备树用来指定引脚资源) 二、编写驱动 2.1 i2c_drv_read 2.2 i2c_drv_write 2.3 完整驱动程序 三、上机测试 放在哪个I2C控制器下面 AT24C02的I2C设备地址(查

    2024年02月11日
    浏览(36)
  • 嵌入式Linux-IMX6ULL开发环境配置

    正点原子的驱动开发指南所使用的是纯净的Ubuntu16.04平台,用户可以按照驱动开发指南的学习路线,一步一步地亲手把Ubuntu平台搭建完成。 但是学习正点原子Linux开发板,个人还是强烈推荐大家安装正点原子提供的虚拟机平台!!!! 可以快速进行体验或者开发,避免学习周

    2024年02月03日
    浏览(36)
  • 【IMX6ULL驱动开发学习】05.IMX6ULL驱动开发_编写第一个hello驱动【熬夜肝】

    经过以下四个步骤,终于可以开始驱动开发了 01.安装交叉编译环境【附下载地址】 02.IMX6ULL烧写Linux系统 03.设置IMX6ULL开发板与虚拟机在同一网段 04.IMX6ULL开发板与虚拟机互传文件 一、获取内核、编译内核 二、创建vscode工作区,添加内核目录和个人目录 三、了解驱动程序编写

    2024年02月06日
    浏览(37)
  • iMX6ULL驱动开发 | 让imx6ull开发板支持usb接口FC游戏手柄

    手边有一闲置的linux开发板iMX6ULL一直在吃灰,不用来搞点事情,总觉得对不住它。业余打发时间就玩起来吧,总比刷某音强。从某多多上买来一个usb接口的游戏手柄,让开发板支持以下它,后续就可以接着在上面玩童年经典游戏啦。  我使用的是正点原子的I.MX6U-ALPHA 开发板,

    2024年02月14日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包