Linux mx6ull-驱动(1)hello

news2024/11/27 0:48:57

编写第一个驱动,hello_drv

一、获取内核、编译内核。

这里为什么要获取内核呢,因为我们写的是驱动程序,而不是裸机程序。也就是我们的板子已经烧入进去了uboot、内核,根文件。然后我们要在这个板子的内核的基础上,来编写实现我们需要功能的代码,那么也就是说,我们的驱动代码是依赖于我们的内核的,那为什么需要编译我们的内核呢,这里简单点说就是我们的内核是经过编译后,生成了一些文件,然后烧入进去板子的,那么我们的驱动文件是依赖于这份编译后的内核的,那么我们的驱动代码也需要编译,这里的内核你可以理解成,就像51单片机写代码的时候,需要引入reg52.h,等头文件一样。不多说上正文。

获取内核文件

        获取Linux内核文件,可以从Linux Kernel官网下载,这里我使用的是正点原子的mx6ull nand板子,为了跟开发板中的系统一致,避免出现其他问题,所以使用的是linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek,需要的话直接去正点原子liunx开发板A光盘下的例程源码下的03、正点原子Uboot和Linux出厂源码下载就可以了,用韦东山的也可以,不过需改改下网卡驱动口,比较麻烦就不弄了,需要注意的一点是,这里获取的内核,必须和开发板的烧入的内核一样,如果不一样可能出现驱动代码不兼容的情况。

链接:https://pan.baidu.com/s/1f3nxEbToe-FVZc1oBHDw3A?pwd=0106 
提取码:0106

编译内核

在编译之前,要在~/.bashrc文件下添加两行内容,来指定编译的平台和工具链

vim ~/.bashrc

在行尾添加或修改,加上下面几行

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

 然后是打开内核压缩包所在文件夹,如图

 输入如下命令解压文件

 tar -vxjf linux-imx-4.1.15-2.1.0-g3dc0a4b-v2.7.tar.bz2

 然后进入文件中,打开终端,开始编译内核,输入如下命令:

新建一个shell脚本:

touch imx6ull_alientek_nand.sh

 在脚本文件里面添加以下内容:

#!/bin/sh 
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alientek_nand_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

 运行脚本:

./imx6ull_alientek_nand.sh

注意期间要是出现图形化界面配置,直接点击两次Esc退出就可以了,然后等待编译完成。
 

二、建立VScode文件,添加路径。

新建一个文件夹,用于保存我们的驱动文件,也就是工程目录。然后利用VSCode打开。然后在文件夹下面,新建一个.vscode文件夹。

 使用快捷键shift+ctrl+p,进入配置,点击下面这个配置选择,会自动生成c_cpp_properties.josn文件.

 把里面内容换成这个

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include", 
                "/home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include", 
                "/home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/",
                "/home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/asm/mach/"              
            ],
            "defines": [],
            "compilerPath": "/usr/bin/clang",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}

 注意,这里的/home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/,也就是你们内核所在的路径,如果你们路径不是这个的话就自己手动改一下。

三、了解字符型驱动的编写步骤.

Linux 下的应用程序是如 何调用驱动程序的,Linux 应用程序对驱动程序的调用如图

Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启 动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在

Linux 内核启动以后使用“insmod”命令加载驱动模块。在调试驱动的时候一般都选择将其编译 为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。 而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。总之,将驱动编 译为模块最大的好处就是方便开发,当驱动开发完成,确定没有问题以后就可以将驱动编译进

Linux 内核中,当然也可以不编译进 Linux 内核中,具体看自己的需求。

今天我们编写的数字符型驱动,步骤大概如下

1.引入头文件

2.定义并创建设备信息结构体

3.定义并创建字符设备的文件操作函数结构体

4.编写对应的字符设备的文件操作函数

5.编写入口函数来注册驱动程序,告诉内核:我来啦。

编写入口函数具体实现:

(1)动态分配设备号使用alloc_chrdev_region()函数或register_chrdev_region()函数动态分配字符设备号。这些函数将为字符设备分配主设备号和次设备号。

(2)初始化字符设备结构体:调用cdev_init()函数来初始化字符设备结构体。该函数需要传入要初始化的struct cdev变量以及指向文件操作函数表的指针。

(3)创建字符设备节点:调用cdev_add()函数将字符设备添加到内核中,在/dev/目录下创建相应的字符设备节点。

(4) 使用 class_create() 函数创建一个设备类,设备类用于在/sys/class目录下创建子目录,以组织同一类设备的相关信息。

(5) 使用 device_create() 函数创建一个设备,并在/dev目录下创建相应的设备节点

6.有入口函数就有出口函数:卸载驱动程序时就会去调用这个出口函数。在模块卸载时,使用 cdev_del() 函数注销字符设备驱动。

(1)使用 unregister_chrdev_region() 函数注销设备号,释放设备号资源。

(2)使用device_destroy销毁设备,删除相应的设备节点

(3)使用class_destroy销毁设备类,释放相关资源

7.调用函数把我们写的出口函数和入口函数告诉内核。

(1)使用module_init(hello_init)把我们编写的入口函数添加进去

(2)使用module_exit(hello_exit)把我们编写的出口函数添加进去

四、编写字符型驱动代码。

1.引入头文件。

这里我们可以仿照下别人的写法,打开内核目录下的drivers/char/1302目录(看名字就猜到该目录下存放应该是字符驱动代码

接下来就是按照我上面的步骤走了。

1.引入头文件

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/major.h>

2.定义并创建设备信息结构体

/* newchrl设备信息结构体 */
struct newchr_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

struct newchr_dev newchr;

3.定义并创建字符设备的文件操作函数结构体

/* 2. 定义自己的file_operations结构体                                              */

static struct file_operations hello_drv = {
	.owner	 = THIS_MODULE,
	.open    = hello_drv_open,
	.read    = hello_drv_read,
	.write   = hello_drv_write,
	.release = hello_drv_close,
};

4.编写对应的字符设备的文件操作函数

/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */

static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)

{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_to_user(buf, kernel_buf, MIN(1024, size));
	return MIN(1024, size);
}

static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)

{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(kernel_buf, buf, MIN(1024, size));
	return MIN(1024, size);
}

static int hello_drv_open (struct inode *node, struct file *file)

{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int hello_drv_close (struct inode *node, struct file *file)

{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

5.编写入口函数来注册驱动程序,告诉内核:我来啦。

static int __init hello_init(void)

{

	/* 动态注册字符设备的流程一般如下:
	1.调用 alloc_chrdev_region() 函数申请设备编号。
	2.使用 cdev_init() 函数初始化设备描述结构体。
	3.使用 cdev_add() 函数将设备号与设备描述结构体关联起来,注册字符设备驱动。
	4.使用 class_create() 函数创建一个设备类.
	5.使用 device_create() 函数创建一个设备
	*/
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/*1 创建设备号
	   根据是否定义了设备号,通过条件判断选择不同的创建方式。

	   如果定义了设备号,则使用MKDEV宏将主设备号和次设备号合成为设备号,并调用register_chrdev_region()函数注册字符设备号。

	   如果没有定义设备号,则使用alloc_chrdev_region()函数动态分配设备号,并通过MAJOR和MINOR宏获取分配得到的主设备号和次设备号。*/
	if (newchr.major) 

	{		/*  定义了设备号 */

		newchr.devid = MKDEV(newchr.major, 0);

		/*register_chrdev_region() 是 Linux 内核中用于向系统申请指定范围内的字符设备编号的函数*/

		register_chrdev_region(newchr.devid, NEWCHR_CNT, NEWCHR_NAME);

	} 
	else 
	{						/* 没有定义设备号 */

		alloc_chrdev_region(&newchr.devid, 0, NEWCHR_CNT, NEWCHR_NAME);	/* 申请设备号 */

		newchr.major = MAJOR(newchr.devid);	/* 获取分配号的主设备号 */

		newchr.minor = MINOR(newchr.devid);	/* 获取分配号的次设备号 */

	}

	/* 2 初始化cdev

	   设置cdev结构体的拥有者为当前模块(THIS_MODULE),然后使用 cdev_init() 函数初始化cdev结构体。

	   参数包括待初始化的cdev结构体和用于操作该设备的file_operations结构体(hello_drv) */

	newchr.cdev.owner = THIS_MODULE;
	cdev_init(&newchr.cdev, &hello_drv);



	/* 3、添加一个cdev */

	cdev_add(&newchr.cdev, newchr.devid, NEWCHR_CNT);


	/*4 创建设备类

	   使用 class_create() 函数创建一个设备类,设备类用于在/sys/class目录下创建子目录,以组织同一类设备的相关信息。

	   该函数的参数包括所属的模块(THIS_MODULE)和设备类的名称(NEWCHR_NAME)。

	   如果创建失败,IS_ERR() 函数将返回true,表示出错,此时使用 PTR_ERR() 函数返回错误码。 */

	newchr.class = class_create(THIS_MODULE, NEWCHR_NAME);
	if (IS_ERR(newchr.class)) {
		return PTR_ERR(newchr.class);
	}



	/*5 创建设备

	   使用 device_create() 函数创建一个设备,并在/dev目录下创建相应的设备节点。

	   参数包括设备所属的类(newchr.class)、父设备(NULL,如果没有父设备)、设备号(newchr.devid)、设备私有数据(NULL,一般为设备驱动程序提供一个指针)和设备名称(NEWCHR_NAME)。

	   如果创建失败,IS_ERR() 函数将返回true,表示出错,此时使用 PTR_ERR() 函数返回错误码。 */

	newchr.device = device_create(newchr.class, NULL, newchr.devid, NULL, NEWCHR_NAME);

	if (IS_ERR(newchr.device)) {
		return PTR_ERR(newchr.device);
	}
	printk("newchr init!\r\n");
	return 0;

}

6.有入口函数就有出口函数:卸载驱动程序时就会去调用这个出口函数。在模块卸载时,使用 cdev_del() 函数注销字符设备驱动。

/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */

static void __exit hello_exit(void)

{

	/*在模块卸载时,使用 cdev_del() 函数注销字符设备驱动,并使用 unregister_chrdev_region() 函数释放设备号资源。*/

	/* 注销字符设备驱动 */

	cdev_del(&newchr.cdev);/*  删除cdev */

	unregister_chrdev_region(newchr.devid, NEWCHR_CNT); /* 注销设备号 */



	device_destroy(newchr.class, newchr.devid);// 销毁设备,删除相应的设备节点

	class_destroy(newchr.class);// 销毁设备类,释放相关资源

	printk("hello_drv exit!\r\n");

}

 7.调用函数把我们写的出口函数和入口函数告诉内核。

最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否 则的话编译的时候会报错,作者信息可以添加也可以不添加。

/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(hello_init);

module_exit(hello_exit);



MODULE_LICENSE("GPL");

MODULE_AUTHOR("oudafa");

五、编写驱动对应应用代码。

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <stdio.h>

#include <string.h>

#include "stdlib.h"



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/newchr", O_RDWR);

	if (fd == -1)

	{

		printf("can not open file /dev/newchr\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\r\n", buf);

	}

	close(fd);

	

	return 0;

}

六、编写Makefile。

KERN_DIR = /home/odf/linux-imx/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

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

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

obj-m	+= newchar.o

七、验证hello驱动。

使用make命令把上面驱动生成的.ko文件还有APP文件 通过nfs挂载的方式,传输到开发板上,然后复制到开发板的/lib/modules/4.1.15/下,然后使用insmod命令加载模块。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1190578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023-11-09 node.js-有意思的项目-记录

摘要: 2023-11-09 node.js-有意思的项目-记录 记录: 1、 NodeBB Star: 13.3k 一个基于Node.js的现代化社区论坛软件&#xff0c;具有快速、可扩展、易于使用和灵活的特点。它支持多种数据库&#xff0c;包括MongoDB、Redis和PostgreSQL&#xff0c;并且可以轻松地进行自定义…

软件测试|MySQL LIKE:深入了解模糊查询

简介 在数据库查询中&#xff0c;模糊查询是一种强大的技术&#xff0c;可以用来搜索与指定模式匹配的数据。MySQL数据库提供了一个灵活而强大的LIKE操作符&#xff0c;使得模糊查询变得简单和高效。本文将详细介绍MySQL中的LIKE操作符以及它的用法&#xff0c;并通过示例演示…

Linux系统环境变量

Linux系统环境变量 1. 环境变量1.1 定义环境变量1.2 使用环境变量 2. 环境变量作用2.1 PATH 3. 设置环境变量3.1 设置系统级环境变量3.2 设置用户级环境变量3.3 设置临时环境变量 1. 环境变量 在Linux系统中&#xff0c;常用的关键信息以键值对&#xff08;KeyValue&#xff09…

第四章:人工智能深度学习教程-激活函数(第二节-ANN 中激活函数的类型)

生物神经网络以人工神经网络的形式建模&#xff0c; 其中人工神经元模拟生物神经元的功能。人工神经元如下图所示&#xff1a; 人工神经元的结构 每个神经元由三个主要部分组成&#xff1a; 一组“i”个突触&#xff0c;其权重为 w i。信号 x i形成具有权重 w i的第 i 个突触的…

关于稳定扩散最详细的介绍

推荐基于稳定扩散(stable diffusion) AI 模型开发的自动纹理工具&#xff1a; DreamTexture.js自动纹理化开发包 - NSDT Stable Diffusion 用途广泛&#xff0c;因为它可以以多种不同的方式使用。首先&#xff0c;让我们关注仅从文本 &#xff08;text2img&#xff09; 生成图像…

“箭在弦上”的边缘计算,更需要冷静和智慧

AI大模型、云游戏、自动驾驶、工业互联网等新一代数字技术&#xff0c;会给各行各业和社会经济生活带来巨大改善&#xff0c;这是大家都知道的。 要实现我们所期待的产业变化&#xff0c;一个完整的AI计算架构应该是&#xff1a;云侧端侧边缘侧&#xff0c;进行高效有序的协同。…

Mysql数据库 12.SQL语言 触发器

一、触发器&#xff08;操作日志表&#xff09; 1.介绍 不需要主动调用的一种储存过程&#xff0c;是一个能够完成特定过程&#xff0c;存储在数据库服务器上的SQL片段。 对当前表中数据增删改查的一种记录<日志表>&#xff0c;根据触发器自动执行&#xff0c;记录当前…

【阿里云】任务2-OSS对象存储教程(找我参加活动可获得京东卡奖励)

目录 前言说明第一步第二步第三步&#xff1a;开通并使用OSS传输加速三、清理第四步-提交作品第五步-提交记录到小程序 前言 本次任务是阿里云官方发出的&#xff0c;每个任务30软妹币&#xff0c;欢迎大家加入我的活动群&#xff0c;门槛很低&#xff0c;所有人都可以参加&…

kkFileView getCorsFile 任意文件读取漏洞(CVE-2021-43734)

kkFileView getCorsFile 任意文件读取漏洞 &#xff08;CVE-2021-43734&#xff09; 免责声明漏洞描述漏洞影响漏洞危害网络测绘Fofa: body"kkFileView" 漏洞复现1. 构造poc2. 读取文件 免责声明 仅用于技术交流,目的是向相关安全人员展示漏洞利用方式,以便更好地提…

用于汽车主驱逆变器的NVVR26A120M1WST、NVVR26A120M1WSS、NVVR26A120M1WSB 1200V、碳化硅(SiC)模块

碳化硅&#xff08;SiC&#xff09;模块 – EliteSiC主驱逆变器功率模块 1200V&#xff0c;半桥&#xff0c;介绍 1、&#xff08;NVVR26A120M1WST&#xff09;功率模块是用于混合动力车&#xff08;HEV&#xff09;和电动车&#xff08;EV&#xff09;主驱逆变器应用的VE-Tra…

​​​​​​​​​​​​​​汽车网络信息安全分析方法论

目录 1.典型信息安全分析方法 1.1 HEAVENS威胁分析模型 1.2 OCTAVE威胁分析方法 1.3 Attack Trees分析方法 2. 功能安全与信息安全的关系讨论 与Safety的典型分析方法一样&#xff0c;Security也有一些典型的信息安全威胁分析方法(TARA分析)&#xff0c;根据SAE J3061、I…

C++: 类和对象(下) (初始化列表, 隐式类型转换, static成员, 友元, 内部类, 匿名对象)

文章目录 一. 再谈构造函数1. 构造函数体赋值2. 初始化列表3. explitcit 关键字 二. static 成员1. 概念2. 特性声明静态成员使用类的静态成员定义静态成员 三. 友元1. 友元函数2. 友元类 四. 内部类五. 匿名对象 一. 再谈构造函数 1. 构造函数体赋值 在创建对象时, 编译器通过…

一张数学地图带你尽览数学分支

我们在学校学习的数学可能也只是数学领域的冰山一角&#xff0c;作为庞大而多样的学科&#xff0c;我今天将通过一张数学地图带你尽览数学分支。 本数学地图对应的视频讲解地址如下&#xff1a; https://www.youtube.com/watch?vOmJ-4B-mS-Y 另外&#xff0c;由于图片较大&a…

STM32中断简介

中断系统 中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行&#xff1b; 以上是中断的…

【Linux】JREE项目部署与发布

目录 一.jdk安装配置 1.1.传入资源 1.2. 解压 1.3. 配置 二.Tomcat安装 2.1.解压开启 2.2. 开放端口 三.MySQL安装 3.1.解压安装 3.2.登入配置 四.后端部署 今天就到这里了哦&#xff01;&#xff01;希望能帮到你哦&#xff01;&#xff01;&#xff01; 一.jdk…

半导体(芯片)制造工艺流程简单说

半导体行业是国民经济支柱性行业之一&#xff0c;是信息技术产业的重要组成部分&#xff0c;是支撑经济社会发展和保障国家安全的战略性、基础性和先导性产业&#xff0c;其发展程度是衡量一个国家科技发展水平的核心指标之一&#xff0c;属于国家高度重视和鼓励发展的行业。 …

ant design pro of vue怎么使用阿里iconfont

一 使用离线iconfont 首先需要生成图所有图标对应的js文件。如下图所示&#xff0c;将生成的js代码复制&#xff0c;在项目中创建一个js文件&#xff0c;将代码粘贴进去。这里我将js文件放在了src/assets/iconfont下面 然后&#xff0c;在main.js中引入文件&#xff0c;并进…

强化IP地址管理措施:确保网络安全与高效性

IP地址管理是网络安全和性能管理的关键组成部分。有效的IP地址管理可以帮助企业确保网络的可用性、安全性和高效性。本文将介绍一些强化IP地址管理的关键措施&#xff0c;以帮助企业提高其网络的安全性和效率。 1. IP地址规划 良好的IP地址规划是强化IP地址管理的基础。它涉及…

【操作系统】考研真题攻克与重点知识点剖析 - 第 2 篇:进程与线程

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图与王道考研课程&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术…

基于SSM框架的管理系统-计算机毕设 附源码 23402

基于SSM框架的管理系统 摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。在目前的形势下&#xff0c;无论是从国家到企业再到家庭&#xff0c;计算机都发挥着其不可替代的作用&#xff0c;可以说…