草庐IT

第四章 linux字符设备驱动一

yiyilearnlinux 2023-03-28 原文

前言

字符设备是Linux驱动中三大设备之一,字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少要实现open、close、read和write的系统调用。字符终端(/dev/console)和串口(/dev/ttyS0以及类似备)就是两个字符设备,它们能很好的说明“流”这种抽象概念。字符设备可以通过文件节点来访问,比如/dev/tty1和/dev/lp0等。这些设备文件和普通文件之间的唯一差别在于对普通文件的访问可以前后移动访问位置,而大多数字符设备是一个只能顺序访问的数据通道。然而,也存在具有数据区特性的字符设备,访问它们时可前后移动访问位置。


一、字符设备的编写步骤

1.实现入口函数xxx_init()和卸载函数xxx_exit()
2.申请设备号register_chrdev(与内核有关)
3.注册字符设备驱动cdev_alloc cdev_init cdev_add(与内核有关)
4.利用udev/mdev机制创建设备文件(节点),
class_create,device_create(与内核有关)
5.硬件部分初始化 io资源映射ioremap,内核提供gpio库函数(与硬件相关)
注册中断(与硬件相关)
初始化等待队列(与内核有关)
初始化定时器(与内核有关)
6.构建file_operation结构(与内核相关)
实现操作硬件方法xxx_open,xxx_read,xxx_write...(与硬件有关)

二、字符设备应用

1.视频教学中的使用

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

		module_init(chr_dev_init);
	    module_exit(chr_dev_exit);

2.在模块加载入口函数中
A.申请主设备号(内核中用于区分和管理不通风字符设备)

register_chrdev(unsigned int major, const char * name, const struct file_operations * fops		

B.创建设备节点文件(为用户提供哟个课操作到文件接口---open());

struct class class_create(owner, name)
struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)

C.硬件的初始化
1.地址的映射

gpx2conf = ioremp(GPX2_CON,GPX2_SIZE);

2.中断的申请
3.实现硬件的寄存器的初始化
//需要1配置gpio功能为输出

		*gpx2conf &= ~(0xf<<28);
		*gpx2conf |=(0x1<<28);

D.实现file_operations

const struct file_operations my_fops= {
	.open = chr_drv_open,
	.read = chr_drv_read,
	.write = chr_drv_write,
	.release = chr_drv_close,
};

规范:
1.
//0,实例化全局的设备对象--分配空间
//GFP_KERNEL如果当前空间内存不够用的时候,该函数会一直阻塞(休眠)

//#include <linux/slab.h>
led_dev = kmalloc(sizeof(struct  led_desc), GFP_KERNE);
If(led_dev == NULL)
{
printk(KERN_ERR “malloc error\n”);
return -ENOMEM;
}
led_dev->dev_major = 250;

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

led_dev->dev_major = register_che_dev(0, “led_dev_test”,&my_fops);
if(led_dev->dev_major < 0)
{
printk(KERN_ERR “register_chrdev error\n”);
ret = -ENODEV;
goto ↓err_0;
}

err_0:
kfree(led_dev);
return ret;

2.个人观点

按照视频的来最终在使用时,可能会遇到一些bug,比如:
在使用kzalloc申请内存的时候总是申请失败,在参考网上的解决办法后依旧未能解决,实例化对象需要用到kzalloc这一种方法在我学习的时候,并没有得到验证,始终卡在内存申请这儿,让后我使用传统方法,才达到自己想要的结果。


总结

字符设备驱动编写方法有多种,其中区别主要在于硬件资源的获取:传统字符设备驱动编写;平台总线;设备树。前面两种本质上区别其实并不大,但引入了分层的思想。后续将慢慢和大家分享。

附录

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>

/* 寄存器物理地址 */
#define CCM_CCGR1_BASE				(0X020C406C)	
#define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)
#define GPIO1_DR_BASE				(0X0209C000)
#define GPIO1_GDIR_BASE				(0X0209C004)

/* 映射后的寄存器虚拟地址指针 */
static volatile unsigned int  *IMX6U_CCM_CCGR1;
static volatile unsigned int  *SW_MUX_GPIO1_IO03;
static volatile unsigned int  *SW_PAD_GPIO1_IO03;
static volatile unsigned int  *GPIO1_DR;
static volatile unsigned int  *GPIO1_GDIR;

//静态指定
struct led_desc {
	unsigned int dev_major;//设备号
	struct class *cls;
	struct device *dev;//创建设备文件
	/* 映射后的寄存器虚拟地址指针 */

};

 struct led_desc *led_dev;
static int kernel_val = 555;
//read(fd,buf,size);
ssize_t chr_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
	int ret;
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	ret = copy_to_user(buf, &kernel_val, count);
	if(ret > 0)
	{
		printk("failed copy_to_user\r\n");
		return -EFAULT;
	}

	return 0;
}

ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
	int ret;
	int value;
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	ret = copy_from_user(&value, buf, count);
	if(ret > 0)
	{
		printk("failed copy_from_user\r\n");
		return -EFAULT;
	}
	
	if(value){
		*GPIO1_DR |= (1 << 3);
	}else{
		*GPIO1_DR &= ~(1 << 3);
	}
	
	return 0;
}

int chr_drv_open (struct inode *inode, struct file *filp)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	return 0;
}

int chr_drv_close (struct inode *inode, struct file *filp)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	return 0;
}

const struct file_operations my_fops  = {
	.open = chr_drv_open,
	.read = chr_drv_read,
	.write = chr_drv_write,
	.release = chr_drv_close,
};

/*
②在模块加载入口函数中
	a  申请主设备号(内核中用于区分和管理不同字符设备)
		register_chrdev()
	b  创建设备节点文件(为用户提供一个可操作到文件接口--open())
		struct class *class_create()
		struct device *device)create()
	c  硬件初始化
		1.地址映射
		2.中断申请
		3.实现硬件的寄存器的初始化
	d  实现file_operation
*/
static int __init chr_dev_init(void)
{
	int ret;

	//0 实例化全局的设备对象
	led_dev = kzalloc(sizeof(struct led_desc), GFP_KERNEL);
	if(led_dev == NULL);
	{
		printk("kmalloc failed\r\n");
		return -ENOMEM;
	}

	//一般都是申请设备号资源
	//1.申请设备号
	led_dev->dev_major = register_chrdev(0, "chr_dev_test", &my_fops);
	if(led_dev->dev_major < 0){
		printk(KERN_ERR"register_chrdev failed\n");
		ret = -ENODEV;
		goto err_0;

	}

	//2.创建设备文件
	led_dev->cls = class_create(THIS_MODULE, "chr_cls");
	if(IS_ERR(led_dev->cls))
	{
		printk(KERN_ERR"class_create failed\n");
		ret = PTR_ERR(led_dev->cls);//将指针出错原因转换为一个出错码
		goto err_1;
	}
	// /dev/led0
	led_dev->dev = device_create(led_dev->cls, NULL, MKDEV(led_dev->dev_major, 0), NULL, "led%d", 0);
	if(IS_ERR(led_dev->dev))
	{
		printk(KERN_ERR"device_create failed\n");
		ret = PTR_ERR(led_dev->dev);//将指针出错原因转换为一个出错码
		goto err_2;
	}
	//3,硬件初始化
	/* 初始化LED */
	/* 1、寄存器地址映射 */
  	IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);

	SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);

  	SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);

	GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);

	GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);


	/* enable GPIO1
	 * configure GPIO1_io3 as gpio
	 * configure GPIO1_io3 as output 
	 */
	*IMX6U_CCM_CCGR1 |= 0x0C000000;
	
	*SW_MUX_GPIO1_IO03 &= 0xfffffff0;
	*SW_MUX_GPIO1_IO03 |= 0x5;

	*SW_PAD_GPIO1_IO03 &= 0xfffffff0;
	*SW_PAD_GPIO1_IO03 |=  0x10B0;


	*GPIO1_GDIR &= 0xfffffff0;
	*GPIO1_GDIR |= 0x8;


/*
readl   writel
static inline u32 readl(const volatile void __iomem *addr)

static inline void writel(u32 value, void *addr)
 2、使能GPIO1时钟 
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26);	 清楚以前的设置 
	val |= (3 << 26);	设置新值 
	writel(val, IMX6U_CCM_CCGR1);

	 3、设置GPIO1_IO03的复用功能,将其复用为
	    GPIO1_IO03,最后设置IO属性。
	 
	writel(5, SW_MUX_GPIO1_IO03);
	
	寄存器SW_PAD_GPIO1_IO03设置IO属性
	 bit 16:0 HYS关闭
	 bit [15:14]: 00 默认下拉
     bit [13]: 0 kepper功能
     bit [12]: 1 pull/keeper使能
     bit [11]: 0 关闭开路输出
     bit [7:6]: 10 速度100Mhz
     bit [5:3]: 110 R0/6驱动能力
     bit [0]: 0 低转换率
	
	writel(0x10B0, SW_PAD_GPIO1_IO03);

	 4、设置GPIO1_IO03为输出功能 
	val = readl(GPIO1_GDIR);
	val &= ~(1 << 3);	 清除以前的设置 
	val |= (1 << 3);	 设置为输出 
	writel(val, GPIO1_GDIR);

	 5、默认关闭LED 
	val = readl(GPIO1_DR);
	val |= (1 << 3);	
	writel(val, GPIO1_DR);
或
writel(readl(led_dev->GPIO1_DR) | (1 << 3),led_dev->GPIO1_DR);


*/
	
	return 0;

err_2:
	class_destroy(led_dev->cls);
err_1:
	unregister_chrdev(led_dev->dev_major, "chr_dev_test");
err_0:
	kfree(&led_dev);
	return	ret;

	return ret;
}

static void __exit chr_dev_exit(void)
{
	//一般都是释放资源
		/* 取消映射 */
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);
	
	device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));
	class_destroy(led_dev->cls);
	unregister_chrdev(led_dev->dev_major, "chr_dev_test");

	

}
//①实现模块的加载和卸载入口函数
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");



有关第四章 linux字符设备驱动一的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  3. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  4. ruby-on-rails - unicode 字符串的长度 - 2

    在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)

  5. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  6. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  7. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  8. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  9. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

  10. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

随机推荐