Linux驱动开发(三)--新字符设备驱动开发 LED驱动开发升级

news2025/4/28 4:42:33

1、新字符设备驱动原理

使用 register_chrdev 函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会
带来两个问题
  • 需要我们事先确定好哪些主设备号没有使用
  • 会将一个主设备号下的所有次设备号都使用掉,比如现在设置 LED 这个主设备号为200,那么 0~1048575(2^20-1) 这个区间的次设备号就全部都被 LED 一个设备分走了。这样太浪费次设备号了!一个 LED 设备肯定只能有一个主设备号,一个次设

旧字符驱动模式,如下所示:

新字符驱动模型,如下所示:

2、分配和释放设备号

解决这两个问题最好的方法就是要使用设备号的时候向 Linux 内核申请,需要几个就申请
几个, Linux 内核分配设备可以使用的设备号
如果没有指定设备号的话就使用如下函数来申请设备号:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可
int register_chrdev_region(dev_t from, unsigned count, const char *name)
注销字符设备之后要释放掉设备号 ,不管是通过 alloc_chrdev_region 函数还是
register_chrdev_region 函数申请的设备号,统一使用如下释放函数:
void unregister_chrdev_region(dev_t from, unsigned count)
综上所述,新字符设备驱动下,设备号分配示例代码如下:
 int major; /* 主设备号 */
 int minor; /* 次设备号 */
 dev_t devid; /* 设备号 */
 
 if (major) { /* 定义了主设备号 */
 devid = MKDEV(major, 0); /* 大部分驱动次设备号都选择 0*/
 register_chrdev_region(devid, 1, "test");
 } else { /* 没有定义设备号 */
 alloc_chrdev_region(&devid, 0, 1, "test"); /* 申请设备号 */
 major = MAJOR(devid); /* 获取分配号的主设备号 */
 minor = MINOR(devid); /* 获取分配号的次设备号 */
}

注销设备很简单,因为我们的CNT是1嘛,如下所示:

unregister_chrdev_region(devid, 1);

3、新的字符设备注册方法(cdev)

其中我们要涉及用到cdev,在 Linux 中使用 cdev 结构体表示一个字符设备,cdev 结构体在 include/linux/cdev.h 文件中的定义如下:

 struct cdev {
  struct kobject kobj;
  struct module *owner;
  const struct file_operations *ops;
  struct list_head list;
  dev_t dev;
  unsigned int count;
};
cdev 中有两个重要的成员变量: ops dev ,这两个就是字符设备文件操作函数集合
file_operations 以及设备号 dev_t 。编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个
变量就表示一个字符设备,如下所示:
struct cdev test_cdev;
参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合。
使用 cdev_init 函数初始化 cdev 变量和向 Linux 系统添加字符设备 (cdev 结构体变量)的示例代码如下:
struct cdev testcdev;
 
 /* 设备操作函数 */
 static struct file_operations test_fops = {
 .owner = THIS_MODULE,
 /* 其他具体的初始项 */
};
 
 testcdev.owner = THIS_MODULE;
 cdev_init(&testcdev, &test_fops); /* 初始化 cdev 结构体变量 */
 cdev_add(&testcdev, devid, 1); /* 添加字符设备 */

卸载删除驱动时如下所示:

cdev_del(&testcdev); /* 删除 cdev */

4、创建以及删除类和设备(class、device)

 struct class *class; /* 类 */ 
 struct device *device; /* 设备 */
 dev_t devid; /* 设备号 */ 
 
 /* 驱动入口函数 */
 static int __init led_init(void)
 {
     /* 创建类 */
     class = class_create(THIS_MODULE, "xxx");
     /* 创建设备 */
     device = device_create(class, NULL, devid, NULL, "xxx");
     return 0;
 }

 /* 驱动出口函数 */
 static void __exit led_exit(void)
 {
     /* 删除设备 */
     device_destroy(newchrled.class, newchrled.devid);
     /* 删除类 */
     class_destroy(newchrled.class);
 }

 module_init(led_init);
 module_exit(led_exit);

整体案例代码如下所示:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define NEWCHRLED_CHT    1         /* 设备号个数 */
#define NEWCHRLED_NAME "newchrled" /* 设备名字   */
#define LEDOFF           0         /* 关灯         */
#define LEDON            1         /* 开灯         */

#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 void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

struct newchrled_dev{
	dev_t devid;           /* 设备号 */
	struct cdev cdev;      /* cdev   */
	struct class *class;   /* 类      */
	struct device *device;  /* 设备     */
	int major;             /* 主设备号*/
	int minor;			   /* 次设备号 */
}

struct newchrled_dev newchrled; /* led设备 */

/*
 * @description : LED 打开/关闭
 * @param - sta : LEDON(0) 打开 LED,LEDOFF(1) 关闭 LED
 * @return : 无
 */
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3); 
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF) {
		val = readl(GPIO1_DR);
		val |= (1 << 3);
		writel(val, GPIO1_DR);
	} 
}


static int led_open (struct inode *inodp, struct file *filp)
{
	filp->private_data = &newchrled; 
	return 0;
}

static ssize_t led_read (struct file *filp, char __user *buf, 
									     size_t cnt, loff_t *offt)
{
	return 0;
}

/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write (struct file *filp, const char __user *buf, 
											    size_t cnt, loff_t *offt)
{
	int retvalue = 0;
	unsigned char databuf[1];
	unsigned char ledstat;
	
	retvalue = copy_from_user(databuf,buf,cnt);
	if(retvalue < 0){
		printk("kernel write failed\r\n");
		return -EFAULT;
	}
	ledstat = databuf[0];
	if(ledstat == LEDON){
		led_switch(LEDON);
	}
	else if(ledstat == LEDOFF){
		led_switch(LEDOFF);
	}
	return 0;
}

/*
 * @description : 关闭/释放设备
 * @param - filp : 要关闭的设备文件(文件描述符)
 * @return : 0 成功;其他 失败
 */
static int led_release (struct inode *inodp, struct file *filp)
{
	return 0;
}

static struct file_operations newchrled_fops = {
	.owner   = THIS_MODULE,
	.open    = led_open,
	.read    = led_read,
	.write   = led_write,
	.release = led_release,
};

static int __init led_init(void){
	int retvalue = 0;
	u32 val = 0;

	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);

	/* 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 属性 */
   	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);

	/*	//设备号 名字 字符模型驱动的一个结构体
	retvalue = register_chrdev(LED_MAJOR,LED_NAME,&newchrled_fops);
	if(retvalue < 0){
	// exit
	}	
	*/
	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if(newchrled.major){
		newchrled.devid = MKDEV(newchrled.major, 0);
		register_chrdev_region(newchrled.devid, NEWCHRLED_CHT, NEWCHRLED_NAME);
	} else {
		alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CHT, NEWCHRLED_NAME);
		newchrled.major = MAJOR(newchrled.devid);
		newchrled.minor = MINOR(newchrled.devid);
	}
	printk("newchrled major = %d,minor = %d \r\n",newchrled.major,newchrled.minor);
	/* 2、初始化     cdev */
	newchrled.cdev.owner = THIS_MODULE;
	cdev_init(&newchrled.cdev,&newchrled_fops);
	/* 3、添加一个 cdev */
	cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CHT);
	/* 4、创建类 */
	newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
	if(IS_ERR(newchrled.class)){
		return PTR_ERR(newchrled.class);
	}
	/* 5、创建设备 */
	newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, 
	                                                  NULL, NEWCHRLED_NAME);
	if(IS_ERR(newchrled.device)){
		return PTR_ERR(newchrled.device);
	}
	return 0;
}

static void __exit led_exit(void){
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	/* unregister_chrdev(LED_MAJOR,LED_NAME); */
	/* 注销字符设备 */
	cdev_del(&newchrled.cdev);
	unregister_chrdev_region(newchrled.devid, NEWCHRLED_CHT);
	
	device_destroy(newchrled.class,newchrled.devid);
	class_destroy(newchrled.class);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("7yewh");

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

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

相关文章

Java学习笔记(一)Java内容介绍、程序举例、DOS命令、Java跨平台特性的本质

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍Java内容介绍、程序举例、DOS命令、Java跨平台特性的本质详细介绍以及部分理论知识 🍉欢迎点赞 👍 收藏 ⭐留言评论 📝私信必回哟😁 🍉博主收将持续更新学习记录获,友友们有任何问题可以在评论区留言 目录 1、内容介绍…

U盘格式化后数据能恢复吗?1分钟了解答案!

“想问问大家如果不小心把u盘格式化后&#xff0c;还有机会恢复吗&#xff1f;一个不小心就按下了格式化按钮&#xff0c;现在后悔莫及&#xff0c;不知道应该怎么操作呢。” U盘就像是一个记忆小盒子&#xff0c;里面装满了珍贵的回忆、重要的文件和无数的心血。但某一天&…

Element 进度条样式优化

在开发后台管理系统时&#xff0c;经常会用到进度条这样一个控件&#xff0c;Element UI中提供了progress这样一个组件&#xff0c;如下图所示&#xff1a; 该组件默认的颜色会比较单一&#xff0c;为此时常需要对该组件的样式进行一些优化&#xff0c;以满足实际项目的需求。 …

世界奇观短视频制作,AI加持,新手也能月入上万

在这个数字化的时代&#xff0c;短视频已经成为了人们获取信息和娱乐的重要途径。特别是那些展示世界奇观的短视频&#xff0c;如极端的气候、危险的动物、美丽的自然景观等&#xff0c;这些主题具有很强的吸引力&#xff0c;能够引起观众的兴趣和好奇心。那么&#xff0c;如何…

香港优才计划适合你吗?官方标准、申请条件、适合申请人群分析

香港优才计划适合你吗&#xff1f; 众所周知&#xff0c;拥有香港身份&#xff0c;不仅可以享受到优质的教育资源、税收优惠、以及国际化的商业环境&#xff0c;还能在金融、商业、法律保障和生活品质等方面获得显著的好处。除此之外&#xff0c;获得香港护照&#xff0c;还能…

Springboot获取resources中的文件

1.Springboot以文件的形式获取resources中的文件 import com.google.gson.JsonIOException; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import org.springframework.util.ResourceUtils; import j…

【数据结构与算法】线索二叉树 详解

为什么可在不增加指针域的情况下&#xff0c;对二叉树进行线索化&#xff1f; 不增加指针域&#xff1a;因为可以利用n1个空链域。 在线索二叉树中&#xff0c;为每个节点添加两个标志位&#xff0c;分别表示左指针和右指针是普通的孩子指针还是线索&#xff08;前驱或后继&a…

物联网APP设计艺术:技巧与未来科技的融合

从早期的智能家居&#xff0c;到今天的服装制造、医疗保健、物流运输、汽车工业...越来越多的行业开始使用物联网。物联网技术跨度大&#xff0c;适用范围广&#xff0c;设计师在面对物联网产品的UI设计项目时往往会感到受阻。这是什么原因呢&#xff1f;物联网应用程序界面设计…

【C++】哈希的概念及STL中有关哈希容器的使用

目录 前言一、unordered系列关联式容器1.1 标准库中的unordered_set1.1.1 unordered_set的介绍1.1.2 unordered_set的常用接口说明1.1.2.1 unordered_set对象的常见构造1.1.2.1.1 [无参构造函数](https://legacy.cplusplus.com/reference/unordered_map/unordered_map/)1.1.2.1…

“AI”科普丨Transformer架构图解最强教程!

今天给大家分享一篇关于深度学习模型Transformer的文章。我愿称之为讲解Transformer模型最好的文章。 文章内容主要介绍 Transformer 模型的具体实现&#xff1a; Transformer整体架构Transformer概览引入张量自注意力机制Self-Attention多头注意力机制Mutil-Head Attention位…

【RabbitMQ】一篇文章带你理解消息分发的六种模式

RabbitMQ共有6种工作模式&#xff08;消息分发模式&#xff09;&#xff0c;分别是简单模式、工作队列模式、发布订阅模式、路由模式、主题模式以及RPC模式。 简单模式是最基本的工作模式&#xff0c;也是最简单的消息传递模式。在简单模式中&#xff0c;一个生产者将消息发到…

银行存量客户运营与数字化转型

文章目录 银行运营的基础逻辑银行数字化的需求迷思 银行运营的基础逻辑 “运营”二字看似熟悉&#xff0c;但不同的人理解起来千差万别。商业银行不缺运营&#xff0c;缺少的是在数字化工具深度介入经营行为后各项配套要素的运营。明确运营的基础需求、必要性、目标、主要内容…

如何开发一套基于C#和.NET 6.0手术麻醉系统? 手术麻醉系统源码

如何开发一套基于C#和.NET 6.0手术麻醉系统&#xff1f; 手术麻醉系统源码 基于C#和.NET 6.0开发的手术麻醉系统是一个涉及多个层面的复杂项目。 以下是一个概述性的步骤&#xff0c;帮助你开始这个项目&#xff1a; 一、项目规划和需求分析 1、确定项目目标&#xff1a;明确…

微信小程序简易录音机

首先先创建一个项目&#xff08;想必大家都会啦那就直接开干&#xff09; 首先上html结构 <view class"wx-container"><view id"title">录音机</view><view id"time">{{hours}}:{{minute}}:{{second}}</view>&l…

Ubuntu 20.04安装显卡驱动、CUDA和cuDNN(2024.06最新)

一、安装显卡驱动 1.1 查看显卡型号 lspci | grep -i nvidia我们发现输出的信息中有Device 2230&#xff0c;可以根据这个信息查询显卡型号 查询网址&#xff1a;https://admin.pci-ids.ucw.cz/mods/PC/10de?actionhelp?helppci 输入后点击Jump查询 我们发现显卡型号为RTX …

【html】如何利用hbuilderX 开发一个自己的app并安装在手机上运行

引言&#xff1a; 相信大家都非常想开发一款自己的apk&#xff0c;手机应用程序&#xff0c;今天就教大家&#xff0c;如何用hbuilderX 开发一个自己的app并安装在手机上运行。 步骤讲解&#xff1a; 打开hbuilderX &#xff0c;选择新建项目 2.选择5app,想一个名字&#x…

聊聊最近比较火的AI产品做互联网算法备案

今年AI&#x1f525;了&#xff0c;而且是大火&#xff0c;导致监管部门相继出台相关政策&#xff0c;需要管控下&#xff0c;所以互联网算法备案就自然而然重新被提出来。其实这个互联网算法备案去年就已经开始实施了&#xff0c;去年只有几个大厂比如BAT等互联网巨头等会去弄…

学生管理系统更新(账号系统)

展示 头文件 #pragma once #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h>//输入输出函数 #include<stdlib.h>//动态分配函数和随机函数 #include<windows.h>//控制台程序&#xff0c;用来实现cmd指令&#xff0c;title指令&#xff0c;cls指令等等 …

2024中国宁波-东南亚职业教育产教协同发展校企对接会举办

2024年6月16日&#xff0c;由东南亚教育部长组织技术教育发展中心&#xff08;SEAMEO TED&#xff09;、联合国教科文组织国际农村教育研究与培训中心&#xff08;UNESCO INRULED&#xff09;、中国教育国际交流协会&#xff08;CEAIE&#xff09;三方主办的“2024中国宁波-东南…

深入理解和实现Windows进程间通信(共享内存)

常见的进程间通信方法 常见的进程间通信方法有&#xff1a; 管道&#xff08;Pipe&#xff09;消息队列共享内存信号量套接字 下面&#xff0c;我们将详细介绍共享内存的原理以及具体实现。 什么是共享内存&#xff1f; Windows共享内存&#xff08;Shared Memory in Windo…