Linux驱动——字符设备驱动

目录

一、代码编译环境

二、Linux驱动开发分类

三、字符设备驱动开发流程

1.编译驱动模块代码(Makefile)

2.驱动模块的加载与卸载

四、字符设备驱动编写

1.驱动模块

2. 字符设备驱动编写步骤

2.1 设备号的注册与注销

2.2 设备节点的注册与注销

2.3 实现硬件初始化

2.4 在驱动中实现文件io的接口

2.5 应用程序需要传递数据给驱动

3.应用程序和驱动程序扮演什么角色

4.应用程序编写

5.规范

6.测试


一、代码编译环境

1.使用什么工具来写驱动代码:安装source insight工具

(1)找到软件提示把工具安装激活
(2)把Linux内核代码解压到windows目录中
(3)打开工具添加查看的项目
project🡒new project:
第一个对话框:
第一个文本框:输入工程名字

第二个文本框:路径(默认)

第二个对话框: 在项目源码位置,选择 Linux内核代码位置 点击OK 第三个对话框: 选择需要查看的Linux内核代码的包含的源码文件(要查看的内核目录),单击 add tree。
点击close 退出,
然后 重新打开刚才创建的工程 project→open project,如果提示 同步,点击确定,可能
需要很长时间

(4)编写代码
使用source insight工具编写代码,然后把这个程序拷贝到ubuntu系统中,使用交叉编译工
具进行编译,生成适配与开发板的程序。

二、Linux驱动开发分类

1、字符设备驱动,最多的。

2、块设备驱动,存储。

3、网络设备驱动。

一个设备不说是一定只属于某一个类型。比如USB WIFI,SDIO WIFI,属于网络设备驱动,因为他又有USB和SDIO,因此也属于字符设备驱动。

三、字符设备驱动开发流程

1.编译驱动模块代码(Makefile)

KERNEL_PATH=/home/yky/Code/linux-3.14-fs4412 #内核中的Makefile需要配置交叉编译工具链
obj-m += 模块文件名.o #要编译为模块的文件
all:
    make modules -C $(KERNEL_PATH) M=$(shell pwd) #借助已经编译好的内核,编译模块

# -C 指定内核路径
# M:当前模块的位置

2.驱动模块的加载与卸载

Linux驱动程序可以编译到kernel里面,也就是zImage,也可以编译为模块,.ko。测试的时候只需要加载.ko模块就可以。

编写驱动的时候注意事项!

1、编译驱动的时候需要用到linux内核源码!因此要解压缩linux内核源码,编译linux内核源码。得到zImage和.dtb。需要使用编译后的到的zImage和dtb启动系统。

2、从SD卡启动,SD卡烧写了uboot。uboot通过tftp从ubuntu里面获取zimage和dtb,rootfs也是通过nfs挂载。

3、设置bootcmd和bootargs(根据自身情况进行调整)

set bootargs root=/dev/nfs nfsroot=192.168.3.201:/home/xwq/nfshome/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.3.6

setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;bootm 41000000 - 42000000


4、将编译出来的.ko文件放到根文件系统里面。加载驱动会用到加载命令:insmod,modprobe。移除驱动使用命令rmmod。对于一个新的模块使用modprobe加载的时候需要先调用一下depmod命令。驱动模块加载成功以后可以使用lsmod查看一下。卸载模块使用rmmod命令

(1)insmod:加载模块

insmod 模块路径

(2)rmmod:卸载模块

mkdir /lib/modules
mkdir /lib/modules/3.14.0
rmmod 模块名

(3)lsmod:查看已经加载的模块

四、字符设备驱动编写

1.驱动模块

 (1)符号导出

如果模块中内容需要在其他模块中使用,可以把内容进行导出:添加导出声明
EXPORT_SYMBOL(内容名字);
注:如果模块只用于进行导出,可以不写入口声明以及定义

(2)参数传递 

在编写驱动时,有些变量是不确定的,是根据驱动具体加载到哪个设备才确定,在进行
insmod装载驱动模块时再传递这些参数值。

如:加载模块:insmod perm.ko a=10 b=5 p="okokok"


在驱动代码中如何处理参数传递: module_param(name,type,perm);

参数1:参数的名字,变量名
参数2:参数的类型,int,char
参数3:/sys/modules 文件的权限,0666

2. 字符设备驱动编写步骤

1.实现模块加载和卸载入口函数

/*
1、头文件
2、驱动模块装载入口和卸载入口声明
3、模块装载函数和卸载函数
4、GPL声明
装载入口:当内核加载这个驱动模块时,从那个函数执行(声明,实现)
*/

//头文件
#include <linux/init.h>
#include <linux/module.h>

//模块加载入口函数实现
static int __init 函数名1(void)
{
    //资源的创建,申请,创建驱动
    return 0;
}

//模块卸载入口函数实现
static void __exit 函数名2(void)
{
    //资源的释放,删除驱动
}

//模块入口声明
//装载声明(内核加载的入口指定)
module_init(函数名1);//只要加载就执行其中声明的函数

//卸载声明(内核卸载的入口指定)
module_exit(函数名2);

//GPL开源声明
MODULE_LICENSE("GPL");

2.在模块加载入口
3. 申请设备号(内核中用于区分和管理不同的字符设备驱动)
4. 创建设备节点(为用户提供一个可操作的文件接口---用户操作文件)
5. 实现硬件初始化
(1) 地址的映射
(2) 实现硬件的寄存器的初始化
(3) 中断的申请
6.实现文件io接口(struct file_operations fops结构体)

字符设备驱动要素

a. 必须有一个设备号,用于在内核中,众多的设备驱动进行区分
b. 用户(应用)必须知道设备驱动对应到哪个设备文件(设备节点)——Linux一切皆文件(把所有的设备都看作是文件)
c. 对设备进行操作(驱动),其实就是对文件进行操作,应用空间操作open、read、write等文
件IO时,实际上驱动代码中对应执行的open、read、write函数

2.1 设备号的注册与注销

//申请设备号
int register_chrdev(unsigned int major, const char *name,const structfile_operations *fops)

参数1:
unsigned int major:主设备号
设备号:32bit == 主设备号(12bit) + 次设备号(20bit)
主设备号:表示同一类型的设备
次设备号:表示同一类型中的不同设备
参数有两种设置:
静态:指定一个整数:250----主设备号为250
动态:让内核随机指定,参数为0
参数2:
const char *name:一个字符串,描述设备信息,自定义
参数3:
const struct file_operations *fops:结构体指针,结构体变量的地址(结构体中就是应
用程序和驱动程序函数关联,open、read、write)---文件操作对象,提供open、read、write等驱动
中的函数
返回值:
如果是静态指定主设备号,返回0表示申请成功,返回负数表示申请失败
如果是动态申请,返回值就是申请成功的主设备号
/proc/devices:文件中包含了所有注册到内核的设备

//销毁注销设备号
void unregister_chrdev(unsigned int major,const char * name)

参数1:
unsigned int major:主设备号
参数2:
const char * name:设备信息,自定义

2.2 设备节点的注册与注销

 1.手动创建

创建设备节点---手动
mknod 设备节点名 设备类型 主设备号 次设备号
#如:
mknod /dev/xxx c 250 0

2. 自动创建(通过udev/mdev机制)

//创建一个类(信息)
struct class * class_create(owner,name)

参数1:
owner:一般填写 THIS_MODULE
参数2:
name:字符串首地址,名字,自定义
返回值:
返回值就返回信息结构体的地址

//创建设备节点(设备文件)
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:设备号(主设备+次设备)
Linux内核将设备号分为两部分:主设备号和次设备号。主设备号占用前12位,次设备号占用低20位。
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

参数4:
void *drvdata:私有数据,一般填NULL

参数5、参数6:
const char *fmt, ...:可变参数,表示字符设备文件名(设备节点名)

//销毁设备节点
void device_destroy(struct class * class,dev_t devt)

 2.3 实现硬件初始化

1.控制外设,其实就是控制地址,内核驱动中通过虚拟地址操作,需要把外设控制的物理地址,映射到内核空间

//虚拟地址映射
void *ioremap(phys_addr_t offset, unsigned long size)

参数1:
phys_addr_t offset:物理地址

参数2:
unsigned long size:映射大小

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

//解除映射
void iounmap(void *addr)

 2.操作寄存器地址的方式

(1)通过指针取 *

例如:
volatile unsigned int * gpx1con;
*gpx1con---进行操作

(2)IO内存访问函数

        使用ioremap函数将寄存器的物理地址映射到虚拟地址后,我们就可以直接通过指针访问这些地址,但是Linux内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

 例如:

我们32bit就采用readl()、writel()

//从地址中读取地址空间的值
u32 readl(const volatile void *addr)

//将value值,存储到对应地址中
void writel(u32 value, volatile void *addr)

2.4 在驱动中实现文件io的接口

1.在驱动中实现文件io的接口操作
const struct file_operations fops;结构体中就是 驱动 与 应用程序文件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);
};
//函数指针集合,其实就是接口,表示文件IO函数用函数指针来表示,存储驱动中的关联函数
// 如: read函数指针 = xxxx_ok;表示 read函数,在驱动中的关联函数为xxxx_ok函数

2.  应用调用去调用文件io去控制驱动

open();
read();
write();

2.5 应用程序需要传递数据给驱动

1.将驱动空间拷贝数据给应用空间

//这个功能一般用于驱动中的 read
long copy_to_user(void __user *to,const void *from, unsigned long n)

参数1:
void __user *to:目标地址,应用空间地址

参数2:
const void *from:源地址,内核空间地址

参数3:
unsigned long n:个数

返回值:
成功返回0,失败返回大于0,表示还有多少个没有拷贝完

2.将应用空间数据拷贝到驱动空间

long copy_from_user(void * to,const void __user * from,unsigned long n)

参数1:
void *to:目标地址,内核空间地址

参数2:
const void *from:源地址,应用空间地址

参数3:
unsigned long n:个数

返回值:
成功返回0,失败返回大于0,表示还有多少个没有拷贝完

3.应用程序和驱动程序扮演什么角色

4.应用程序编写

Linux下一切皆文件,首先要open

例如:


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

int main()
{

	int fd = open("/dev/led0",O_RDWR);

	int value = 0;
	while(1)
	{
		scanf("%d",&value);
		if(value > 1)
			break;
		write(fd,&value,4);
	}
	close(fd);

	return 0;
}

5.规范

 (1)在加载入口实现资源申请,需要在卸载入口实现资源释放

//申请资源
register_chrdev();//申请设备号
class_create();
device_create();//创建设备节点
ioremap();//硬件资源映射
------------------------
//释放资源
iounmap();//解除硬件资源映射
device_destroy();//释放设备节点
class_destroy();//释放节点信息类
unregister_chrdev();//释放设备号

(2)出错处理
在某个位置出错,要将之前申请的资源进行释放

static int __init key_init(void)
{
	int ret;
	//申请设备号
	key.major = register_chrdev(0,"key",&fops);
	if(key.major < 0)
	{
		printk("register_chrdev error\n");
		ret = -1;
		goto err_0;
	}

	//创建设备节点

	key.cls = class_create(THIS_MODULE,"key cls");
	if(IS_ERR(key.cls))
	{
		printk("class_create error\n");
		ret = -2;
		goto err_1;
	}

	key.dev = device_create(key.cls,NULL,MKDEV(key.major,0),NULL,"key3");
	if(IS_ERR(key.dev))
	{
		printk("device_create error\n");
		ret = -3;
		goto err_2;
	}

	//硬件设备初始化
	//中断初始化
	struct device_node * np = of_find_node_by_path("/key_int_node");
	if(np == NULL)
	{
		printk("of_find_node_by_path error\n");
		ret = -4;
		goto err_3;
	}

	key.irqno = irq_of_parse_and_map(np,0);

	//申请中断
	if( request_irq(key.irqno,key_handler,IRQF_TRIGGER_FALLING,"this is key","hello world") != 0 )
	{
		printk("request_irq error\n");
		ret = -5;
		goto err_3;
	}

	return 0;
	
err_3:
	device_destroy(key.cls,MKDEV(key.major,0));
	
err_2:
	class_destroy(key.cls);

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

err_0:
	return ret;
}

(3) 面向对象编程思想

用一个结构体来表示一个对象
设计一个类型,描述一个设备的信息

例如:
struct BEEP
{
unsigned int major;
struct class * cls;
struct device * dev;
unsigned int * pwmtcfg0;
};
struct BEEP beep;//表示一个设备对象

 小妙招:文件私有数据

1.在open函数里面设置file->private_data为设备变量(结构体变量);

2.在read、write里要访问设备的时候,直接读取私有数据;

如:  struct newcharled_dev *dev=file->private_data;

6.测试

(1)加载驱动。

modprobe 驱动.ko

(2)进入/dev查看设备文件。

mknod /dev/........

(3)测试

./应用程序名

  • 6
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Linux字符设备驱动开发是指在Linux系统中编写驱动程序,使得用户可以通过字符设备接口来访问硬件设备。这种驱动程序通常用于控制串口、并口、USB设备等。开发Linux字符设备驱动需要掌握Linux内核的基本知识,包括进程管理、内存管理、中断处理、设备驱动等方面。此外,还需要了解字符设备驱动的编写流程、驱动程序的结构和接口等。开发Linux字符设备驱动需要使用C语言和Linux内核编程接口。 ### 回答2: Linux字符设备驱动开发是Linux系统中的一部分,它允许开发人员在Linux系统上使用字符设备,这些字符设备可以包括串口、USB口、网卡等。Linux字符设备驱动开发可帮助开发人员实现各种各样的设备驱动,从而增强Linux系统的功能。 在Linux字符设备驱动的开发过程中,需要注意以下几点: 1. 实现设备驱动的一个基本框架,包括注册设备、设备的初始化,以及对设备进行读写操作等。 2. 开发人员不仅需要熟悉驱动程序开发技术,还需要了解Linux内核系统的相关知识,例如进程、中断、内存管理等。 3. 应该在代码注释中提供详细的文档,以方便其他开发人员进行维护和修改。 4. 在实现字符设备驱动过程中,必须保证安全性和可靠性,防止设备出现故障或者损坏用户的数据。 5. 在测试和维护设备驱动时,需要使用一些常见的工具和技术,例如devfs、udev等。 总之,Linux字符设备驱动开发是一个需要熟练技能和丰富经验的过程。开发人员需要有足够的专业知识和经验来确保设备驱动的高效和稳定性。通过精心设计和开发,Linux字符设备驱动可以提供高性能、高可靠性、易于使用的设备驱动,从而大大增强了Linux系统的功能和灵活性。 ### 回答3: Linux字符设备驱动开发是Linux系统中的一个重要领域。其主要任务是开发一些支持字符设备驱动程序,从而使用户能够在Linux系统中使用各种不同类型的字符设备,例如串口、打印机、读卡器和磁盘等。同时,这些驱动程序还要保证设备完全可靠和高效地工作,确保系统的安全性和性能。 Linux字符设备驱动开发需要掌握以下基本知识: 1.了解Linux系统体系结构和内核架构 Linux系统由内核和用户空间组成,内核作为系统的核心组件,是实现系统功能的主要部分,因此了解内核体系结构和架构是开发Linux字符设备驱动所必须掌握的知识。 2.熟悉字符设备的相关知识 字符设备Linux系统中的一种重要的设备类型,它与其他类型设备不同之处在于它只能逐个字符地进行读写操作。因此需要深入了解字符设备的相关知识,例如驱动的主要功能、驱动程序与设备的交互方式、设备控制结构等。 3.熟练掌握C语言及Linux内核编程技术 编写Linux字符设备驱动程序需要掌握良好的C语言编程知识以及熟练的Linux内核编程技术,包括内存管理、进程管理、文件系统、中断处理等。同时,还需要了解Linux内核代码的结构和代码的编写规范,以便于编写出符合内核标准的驱动程序。 4.掌握Linux驱动框架的使用方法 为了简化Linux驱动的开发流程,Linux提供了一些驱动框架,这些框架定义了一些驱动程序中常用的接口和函数,能够方便驱动程序的开发和调试。因此,Linux字符设备驱动开发者需要掌握其中的一些驱动框架,如字符驱动框架。 5.熟悉Linux字符设备驱动的开发过程 Linux字符设备驱动的开发过程主要包括驱动程序的初始化、驱动程序的主要功能实现、驱动程序的卸载等环节。在开发过程中,需要合理使用系统提供的工具和调试手段,如gdb、strace、make等,以便于分析和排查驱动程序出现的问题,确保驱动程序的稳定和可靠性。 总之,在Linux字符设备驱动开发过程中,开发者需要掌握相关的知识和技能,以实现对字符设备的编程和调试,开发出满足用户需求的高质量驱动程序。同时,Linux字符设备驱动开发也是一项长期持续的工作,开发者需要时刻关注最新的技术发展和硬件设备变化,才能更好地适应市场需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春风从不入睡、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值