Linux mx6ull-驱动(1)hello

编写第一个驱动,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命令加载模块。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【uniapp】签名组件,兼容vue2vue3

网上找了个源码改吧改吧&#xff0c;清除了没用的功能和兼容性&#xff0c;基于uniapp开发的 样子 vue2 使用方法&#xff0c;具体的可以根据业务自行修改 <signature ref"signature" width"100%" height"410rpx"></signature>confi…

[鹏程杯2023]复现

SecretShare X的20个值和R的21个值已经被全部泄露&#xff0c;X和R都是1024bit的值&#xff0c;此时X总共泄露了32*20 640&#xff0c;于是&#xff0c;此时我们可以使用mt19937将其还原&#xff0c;还原之后&#xff0c;我们往前推20个1024bit的值&#xff0c;便可以求得A的…

C语言 预处理详解

目录 1.预定义符号 2.#define 2.1#define 定义标识符 2.2#define 定义宏 2.3#define 替换规则 2.4#和## 2.4.1# 的作用 2.4.2## 的作用 2.5 带有副作用的宏参数 2.6宏和函数的对比 对比 **2.7内联函数 2.8命名约定 3.#undef **4.命令行定义 5.条件编译 常…

AVL树性质和实现

AVL树 AVL是两名俄罗斯数学家的名字&#xff0c;以此纪念 与二叉搜索树的区别 AVL树在二叉搜索树的基础上增加了新的限制&#xff1a;需要时刻保证每个树中每个结点的左右子树高度之差的绝对值不超过1 因此&#xff0c;当向树中插入新结点后&#xff0c;即可降低树的高度&…

闪客网盘系统源码,已测试对接腾讯COS及本地和支付(支持限速+按时收费+文件分享+可对接易支付)- 修复版

正文概述 资源入口 支持对文件下载限速 对接易支付 推广赚钱啥的功能 源码非常的好 支持腾讯cos 阿里云cos 本地储存 远程存储 源码仅支持服务器搭建 php7.2 伪静态thinkphp 运行目录public 导入数据库 修改config目录下的database.php数据库信息 后台地址&#xff1a; 域名/ad…

如何在CentOS上安装SQL Server数据库并通过内网穿透工具实现远程访问

文章目录 前言1. 安装sql server2. 局域网测试连接3. 安装cpolar内网穿透4. 将sqlserver映射到公网5. 公网远程连接6.固定连接公网地址7.使用固定公网地址连接 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;…

CLIP Surgery论文阅读

CLIP Surgery for Better Explainability with Enhancement in Open-Vocabulary Tasks&#xff08;CVPR2023&#xff09; M norm ⁡ ( resize ⁡ ( reshape ⁡ ( F i ˉ ∥ F i ‾ ∥ 2 ⋅ ( F t ∥ F t ‾ ∥ 2 ) ⊤ ) ) ) M\operatorname{norm}\left(\operatorname{resize}\…

文件包含 [ZJCTF 2019]NiZhuanSiWei1

打开题目 代码审计 if(isset($text)&&(file_get_contents($text,r)"welcome to the zjctf")){ 首先isset函数检查text参数是否存在且不为空 用file_get_contents函数读取text制定的文件内容并与welcome to the zjctf进行强比较 echo "<br><h…

【C++】类型转换【4中类型转换】

目录 1. C语言中的类型转换 2. C的四种类型转换 2.1 static_cast 3.2 reinterpret_cast 3.3 const_cast 3.4 dynamic_cast 3. explict 4. RTTI&#xff08;了解&#xff09; 1. C语言中的类型转换 在 C 语言中&#xff0c;如果 赋值运算符左右两侧类型不同&#xff0…

如何用Excel做最小二乘法②

因为在Excel里面做最小二乘法是需要用到LINEST函数的&#xff0c;所以如果不知道怎么对数据进行最小二乘法时&#xff0c;就应该研究一下LINEST函数。 LINEST 函数语法 LINEST(known_ys, [known_xs], [const], [stats]) known_ys (必须) 因变量&#xff0c;单行/单列known_xs…

xxl-job 原理

一、xxl-job 架构设计 总体分两个部分&#xff1a; 调度中心&#xff1a;负责管理调度信息&#xff0c;按照调度配置发出调度请求&#xff0c;自身不承担业务代码。调度系统和任务解耦&#xff0c;提高了系统可用性和稳定性。通调度性能不在受限于任务模块。执行器&#xff1a…

Mysql配置主从复制-GTID模式

目录 主从复制 主从复制的定义 主从复制的原理 主从复制的优势 主从复制的形式 主从复制的模式 主从复制的类型 GTID模式 GTID的概念 GTID的优势 GTID的原理 GTID的配置 Mysql主服务器 ​编辑 Mysql从服务器 ​编辑 主从复制 主从复制的定义 是指把数据从一个…

Docker安装Mysql

Docker常用指令&#xff1a; docker search 镜像名&#xff1a;寻找镜像 docker pull 镜像名&#xff1a;拉去镜像 docker images :查看拥有镜像 docker ps :查看正在运行容器 docker pa -a &#xff1a;查看所有容器&#xff08;包含运行中的和停止的&#xff09; dock…

案例 - 拖拽上传文件,生成缩略图

直接看效果 实现代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>拖拽上传文件</title>&l…

Python 实现动态动画心形图

在抖音上刷到其他人用 matlab 实现的一个动态心形图&#xff0c;就想也用 Python 实现&#xff0c;摸索了两种实现方式&#xff0c;效果如下&#xff1a; 方法一&#xff1a; 利用循环&#xff0c;结合 pyplot 的 pause 与 clf 参数实现图像的动态刷新 import matplotlib.p…

【漏洞复现】weblogic-10.3.6-‘wls-wsat‘-XMLDecoder反序列化(CVE-2017-10271)

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描nacsweblogicScanner3、漏洞验证 说明内容漏洞编号CVE-2017-10271漏洞名称Weblogic < 10.3.…

【Unity细节】VS不能附加到Unity程序中解决方法大全

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

物联网AI MicroPython学习之语法 二进制与ASCII转换

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; ubinascii 介绍 ubinascii模块实现了二进制数据与各种ASCII编码之间的转换。 接口说明 a2b_base64 - 解码base64编码的数据 函数原型&#xff1a;ubinascii.a2b_base64(data)注意事项&#xff1a; 在解码…

GPT-4-Turbo的128K长度上下文性能如何?超过73K Tokens的数据支持依然不太好!

本文原文来自DataLearnerAI官方网站&#xff1a;GPT-4-Turbo的128K长度上下文性能如何&#xff1f;超过73K Tokens的数据支持依然不太好&#xff01; | 数据学习者官方网站(Datalearner)https://www.datalearner.com/blog/1051699526438975 GPT-4 Turbo是OpenAI最新发布的号称…

【MongoDB】索引 – 文本索引

一、准备工作 这里准备一些数据 db.books.insertMany([{_id: 1, name: "Java", description: "java 入门图书", translation: [{ language: "english", description: "java basic book" }]},{_id: 2, name: "C", descript…