Linux驱动开发——新字符设备驱动开发

news2024/12/27 10:58:30

文章目录

  • 1 概述
  • 2 新字符设备驱动原理
    • 2.1 分配和释放设备号
    • 2.2 新字符设备注册方法
  • 3 自动创建设备节点
    • 3.1 mdev机制
    • 3.2 创建和删除类
    • 3.3 创建设备
  • 4 设置文件私有数据
  • 5 实验程序编写

系列文章:
Linux驱动开发——字符设备驱动开发
Linux驱动开发——LED驱动开发

1 概述

之前的字符设备驱动,使用register_chrdev函数注册设备,使用的时候需要mknod命令创建设备节点。register_chrdev和unregister_chrdev是老版本的驱动使用的函数,新的字符设备驱动已经不再使用,而是使用Linux内核推荐的新字符设备驱动API函数。

2 新字符设备驱动原理

2.1 分配和释放设备号

使用 register_chrdev 函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会
带来两个问题:

  • 1、需要我们事先确定好哪些主设备号没有使用。
  • 2、会将一个主设备号下的所有次设备号都使用掉,比如现在设置 LED 这个主设备号为
    200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被 LED 一个设备分走了。这样太浪
    费次设备号了!一个 LED 设备肯定只能有一个主设备号,一个次设备号。

解决这两个问题最好的方式是向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)

from表示要申请的设备号,也就是给定的设备号,count表示要申请的数量,name是设备的名字。
释放设备号的时候,使用以下函数:

void unregister_chrdev_region(dev_t from, unsigned count)

2.2 新字符设备注册方法

  1. 字符设备结构
    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;
} __randomize_layout;

这里面有两个重要的变量:ops和dev,ops是字符设备文件操作函数集合file_operations这个结构体变量,dev则是dev_t类型的设备号。在编写设备驱动之前,需要定义一个cdev类型的变量,表示一个字符设备。
2. cdev_init函数
定义好cdev变量之后,需要通过cdev_init函数对其进行初始化,其声明如下:

void cdev_init(struct cdev *, const struct file_operations *);
  1. cdev_add函数
    cdev_add函数用于向Linux内核中添加字符设备,声明如下:
int cdev_add(struct cdev *, dev_t, unsigned);
  1. cdev_del函数
    cdev_del函数从Linux内核中删除相应的字符设备,其声明如下:
void cdev_del(struct cdev *);

3 自动创建设备节点

之前的驱动开发方式,不会自动创建设备节点,module被加载完成之后,需要手动通过mknod命令创建设备节点,这里配置好之后,加载驱动的时候就会自动创建对应的设备节点

3.1 mdev机制

mdev是busybox自带的一个简化版的udev,mdev和udev都是用于管理虚拟设备的机制,它允许动态地创建、配置、启动和销毁设备文件。可以检测系统中硬件的设备状态,可以根据系统中硬件设备来创建或者删除设备文件。
开发版启动的时候会启动udev
在这里插入图片描述

3.2 创建和删除类

自动创建设备节点的工作是在驱动程序入口函数中完成的,一般在cdev_add函数后面添加自动创建设备节点相关代码。首先要创建一个 class 类,class 是个结构体,定义在文件include/linux/device.h 里面。class_create 是类创建函数,class_create 是个宏定义,内容如下:

extern struct class * __must_check __class_create(struct module *owner,
						  const char *name,
						  struct lock_class_key *key);
extern void class_destroy(struct class *cls);

/* This is a #define to keep the compiler from merging different
 * instances of the __key variable */
#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})

将宏定义展开之后,内容如下:

struct class *class_create (struct module *owner, const char *name)

一共有两个参数,owner一般为THIS_MODULE,参数name是类的名字,返回值是一个指向结构体class的指针,也就是创建的类。
卸载驱动的使用通过class_destroy函数进行卸载

3.3 创建设备

上面创建好类之后还不能自动创建设备节点,还需要在类下创建一个设备,使用device_create函数进行创建

struct device *device_create(struct class *cls, struct device *parent,
			     dev_t devt, void *drvdata,
			     const char *fmt, ...);

是一个可变参数函数,cls表示创建在哪个class下面,parent是父设备,一般为NULL,也就是没有父设备;devt是设备号;drvdata是设备可能用到的一些数据,一般为NULL;fmt是设备名字,如果设置为fmt=xxx,就会创建/dev/xxx设备文件,返回值是创建好的设备。
卸载驱动的时候需要删除掉创建的设备,需要调用函数device_destroy,声明如下:

extern void device_destroy(struct class *cls, dev_t devt);

4 设置文件私有数据

每个硬件设备都有一些属性,比如主设备号(dev_t),类(classs),设备(device),开关状态(state)等,在编写驱动的时候可以将这些属性全部携程变量的形式,可以定义一个结构体进行存储。

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

/* open 函数 */
static int test_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &testdev; /* 设置私有数据 */
	return 0;
}

在open中设置好私有数据之后,在write、read、close等函数中直接读取private_data就可以得到设备结构体。

5 实验程序编写

#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/uaccess.h>
#include <asm/io.h>

#define NEWCHRLED_CNT 1
#define NEWCHRLED_NAME "newchrled"
#define LEDOFF 0
#define LEDON 1


#define PMU_GRF_BASE 0xFDC20000
#define PMU_GRF_GPIO0C_IOMUX_L (PMU_GRF_BASE + 0X0010)
#define PMU_GRF_GPIO0C_DS_0 (PMU_GRF_BASE + 0x0090)

#define GPIO0_BASE 0xFDD60000
#define GPIO0_SWPORT_DDR_H (GPIO0_BASE + 0x000C)
#define GPIO0_SWPORT_DR_H (GPIO0_BASE + 0X0004)

static void __iomem* PMU_GRF_GPIO0C_IOMUX_L_PI;
static void __iomem* PMU_GRF_GPIO0C_DS_0_PI;
static void __iomem* GPIO0_SWPORT_DDR_H_PI;
static void __iomem* GPIO0_SWPORT_DR_H_PI;

struct newchrled_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
};

struct newchrled_dev newchrled;

void led_switch(u8 state) {
    u32 val = 0;
    if (state == LEDON) {
        val = readl(GPIO0_SWPORT_DR_H_PI);
        val &= ~(0x1 << 0);
        val |= ((0x1 << 16) | (0x1 << 0));

        writel(val, GPIO0_SWPORT_DR_H_PI);
    }
    else if (state == LEDOFF) {
        val = readl(GPIO0_SWPORT_DR_H_PI);
        val &= ~(0x1 << 0);
        val |= ((0x1 << 16) | (0x0 << 0));

        writel(val, GPIO0_SWPORT_DR_H_PI);
    }
}

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

ssize_t led_write(struct file* flip, const char __user* buf, size_t cnt, loff_t* offt) {
    int retValue = 0;
    unsigned char dataBuf[1];
    unsigned char ledState;

    retValue = copy_from_user(dataBuf, buf, cnt);
    if (retValue < 0) {
        printk("copy from user error \n");
        return -EFAULT;
    }

    ledState = dataBuf[0];
    if (ledState == LEDON) {
        led_switch(LEDON);
    }
    else if (ledState == LEDOFF) {
        led_switch(LEDOFF);
    }
    else {
        printk("error ledState \n");
    }
    return 0;
}

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

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


void led_remap(void) {
    PMU_GRF_GPIO0C_IOMUX_L_PI = ioremap(PMU_GRF_GPIO0C_IOMUX_L, 4);
    PMU_GRF_GPIO0C_DS_0_PI = ioremap(PMU_GRF_GPIO0C_DS_0, 4);
    GPIO0_SWPORT_DDR_H_PI = ioremap(GPIO0_SWPORT_DDR_H, 4);
    GPIO0_SWPORT_DR_H_PI = ioremap(GPIO0_SWPORT_DR_H, 4);
}

void led_unmap(void) {
    iounmap(PMU_GRF_GPIO0C_IOMUX_L_PI);
    iounmap(PMU_GRF_GPIO0C_DS_0_PI);
    iounmap(GPIO0_SWPORT_DDR_H_PI);
    iounmap(GPIO0_SWPORT_DR_H_PI);
}

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

    led_remap();

    val = readl(PMU_GRF_GPIO0C_IOMUX_L_PI);
    val &= ~(0x7 << 0);
    val |= ((0x7 << 16) | (0x0 < 0));
    writel(val, PMU_GRF_GPIO0C_IOMUX_L_PI);

    val = readl(PMU_GRF_GPIO0C_DS_0_PI);
    val &= (0x3F << 0);
    val |= ((0x3F << 16) | (0x3F << 0));
    writel(val, PMU_GRF_GPIO0C_DS_0_PI);

    val = readl(GPIO0_SWPORT_DDR_H_PI);
    val &= ~(0x1 << 0);
    val |= ((0x1 << 16) | (0x1 << 0));
    writel(val, GPIO0_SWPORT_DDR_H_PI);

    val = readl(GPIO0_SWPORT_DR_H_PI);
    val &= ~(0x1 << 0);
    val |= ((0x1 << 16) | (0x0 << 0));

    if (newchrled.major) { //已定义主设备号
        newchrled.devid = MKDEV(newchrled.major, 0);
        ret = register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
        if (ret < 0) {
            pr_err("cannot register %s char driver [ret=%d]\n", NEWCHRLED_NAME, NEWCHRLED_CNT);
            goto fail_map;
        }
    } else { //未定义主设备号
        ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);
        if (ret < 0) {
            pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", NEWCHRLED_NAME, ret);
            goto fail_map;
        }
        newchrled.major = MAJOR(newchrled.devid);
        newchrled.minor = MINOR(newchrled.devid);
    }
    printk("newchrled major=%d, minor=%d \r\n", newchrled.major, newchrled.minor);
    
    // 初始化cdev变量
    newchrled.cdev.owner = THIS_MODULE;
    cdev_init(&newchrled.cdev, &newchrled_fops);

    // 添加一个cdev到内核
    ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
    if (ret < 0) {
        goto del_unregister;
    }

    // 创建类
    newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
    if (IS_ERR(newchrled.class)) {
        goto del_cdev;
    }

    // 创建设备
    newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
    if (IS_ERR(newchrled.device)) {
        goto destroy_class;
    }

    return 0;

destroy_class:
    class_destroy(newchrled.class);
del_cdev:
    cdev_del(&newchrled.cdev);
del_unregister:
    unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);
fail_map:
    led_unmap();
    return -EIO;
}

static void __exit led_exit(void) {
    led_unmap();

    cdev_del(&newchrled.cdev);
    unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);

    device_destroy(newchrled.class, newchrled.devid);
    class_destroy(newchrled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

测试步骤和Linux驱动开发——LED驱动开发中一样,首先push ko文件进设备,然后insmod加载ko设备,lsmod查看ko文件是否加载成功。
然后使用Linux驱动开发——LED驱动开发中的测试程序进行测试,测试命令

./ledApp /dev/newchrled 1
./ledApp /dev/newchrled 0

正常可以看到led明灭。

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

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

相关文章

24最新从零开始教你玩转ComfyUI-保姆级部署教程-手把手带你ComfyUI工作流搭建!

前言 第一节&#xff1a;认识ComfyUI并安装 本教程专为初学者设计&#xff0c;详细介绍了 2024 年最新版的SD ComfyUI的使用方法。通过逐步指导&#xff0c;让你无需任何基础&#xff0c;快速学会并使用这一强大的AI绘图工具。 1、什么是comfyui ComfyUI就像拥有一支神奇魔…

如何定位前后端Bug?

问题&#xff1a; 假设你在某购物网站上&#xff0c;购买了两件商品&#xff0c;一件打折的&#xff0c;一件不打折的&#xff0c;当你下完订单并且成功支付之后&#xff0c;再去我的订单中查看订单内容时&#xff0c;发现两件商品只显示出来一件&#xff0c;打折的商品并没有显…

最强AI绘画大模型Flux可以在SDWebUI 上使用了!超便捷的Flux模型使用教程!AI绘画零基础入门到实战教程

大家好&#xff0c;我是画画的小强 目前最强的AI绘画大模型Flux.1 横空出世有段时间了&#xff0c;模型效果也得到了广泛的认可&#xff0c;但是 Stable Diffusion WebUI 官方迟迟没有跟进&#xff0c;据说是因为要修改很多底层的处理机制&#xff0c;加之ComfyUI如火如荼&…

基于Springboot的宠物咖啡馆平台的设计与实现(源码+定制+参考)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

高颜值自适应博客主题Puock WordPress主题

一款基于WordPress开发的高颜值的自适应主题&#xff0c;支持白天与黑夜模式。 安装&#xff1a; 请到 发行版本 中进行下载最新版本&#xff0c;然后到WordPress管理后台中的「外观」-「主题」中点击「添加」&#xff0c;选择Puock的主题包进行上传安装并启用即可。 提示&am…

【JVM】深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制

我的主页&#xff1a;2的n次方_ 1. JVM 内存区域划分 程序计数器&#xff08;空间比较小&#xff09;。保存了下一条要执行的指令的地址&#xff08;指向元数据区指令的地址&#xff09;堆。JVM 最大的空间&#xff0c;new 出来的对象都在堆上栈。函数中的局部变量&#x…

【Linux探索学习】第三弹——Linux的基础指令(下)——开启新篇章的大门

Linux基础指令&#xff08;上&#xff09;&#xff1a; 【Linux探索学习】第一弹——Linux的基本指令&#xff08;上&#xff09;——开启Linux学习第一篇-CSDN博客 Linux基础指令&#xff08;中&#xff09;&#xff1a; 【Linux探索学习】第二弹——Linux的基础指令&#…

vmstat命令:系统性能监控

一、命令简介 ​vmstat​ 是一种在类 Unix 系统上常用的性能监控工具&#xff0c;它可以报告虚拟内存统计信息&#xff0c;包括进程、内存、分页、块 IO、陷阱&#xff08;中断&#xff09;和 CPU 活动等。 ‍ 二、命令参数 2.1 命令格式 vmstat [选项] [ 延迟 [次数] ]2…

18734 拓扑排序

### 思路 1. **建模问题**&#xff1a;将课程和依赖关系建模为有向图&#xff0c;其中课程是节点&#xff0c;依赖关系是有向边。 2. **选择算法**&#xff1a;使用拓扑排序算法来确定课程的学习顺序。由于需要确保输出唯一性&#xff0c;同等条件下编号小的课程排在前面&…

Koa学习

Koa 安装与配置 1. 初始化项目 在终端中执行以下命令&#xff1a; # 创建项目文件夹 mkdir koa cd koa# 初始化并安装依赖 npm init -y npm install koa npm install nodemon --save-dev2. 修改 package.json 在 package.json 文件中进行如下修改&#xff1a; {"type…

LeetCode讲解篇之1143. 最长公共子序列

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 这题我们可以采用动态规划求解&#xff0c;用一个二维数组记录text1的0 ~ i区间子串和text2的0 ~ j区间子串的最长公共子序列的长度&#xff0c;我们假设该二维数组是f 这个数组有一个特性&#xff0c;如果a <…

ssm服装店销售管理系统

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习&#xff0c;获取源码请私聊我 需要定制请私聊 目 录 摘 要 I Abstract II 第1章 绪论 1 1.1研究背景 1 1.2研究意义 1 1.3国内外研究现状 2 1.3.1国外研…

R语言中的plumber介绍

R语言中的plumber介绍 基本用法常用 API 方法1. GET 方法2. POST 方法3. 带路径参数的 GET 方法 使用 R 对数据进行操作处理 JSON 输入和输出运行 API 的其他选项其他功能 plumber 是个强大的 R 包&#xff0c;用于将 R 代码转换为 Web API&#xff0c;通过使用 plumber&#x…

启动hadoop后没有 NodeManager和 ResourceManager

跟着黑马网课学下去时发现我的hadoop启动后没有NodeManager和ResourceManager 找到日志的路径 我在/export/server/hadoop/etc/hadoop/hadoop-env.sh文件里配置了日志存放的路径 这里找到你的日志路径&#xff0c;每个人的习惯和看的教程不同&#xff0c;日志放的地方大概率也…

MATLAB中lsqminnorm函数用法

目录 语法 说明 示例 求解具有无限个解的线性系统 指定容差以减少含噪数据的影响 切换显示低秩矩阵警告 lsqminnorm函数的功能是线性方程的最小范数最小二乘解。 语法 X lsqminnorm(A,B) X lsqminnorm(A,B,tol) X lsqminnorm(___,rankWarn) 说明 X lsqminnorm(A,B…

【大语言模型-论文精读】用于医疗领域摘要任务的大型语言模型评估综述

【大语言模型-论文精读】用于医疗领域摘要任务的大型语言模型评估综述 论文信息&#xff1a; 用于医疗领域摘要任务的大型语言模型评估&#xff1a;一篇叙述性综述&#xff0c; 文章是由 Emma Croxford , Yanjun Gao 博士 , Nicholas Pellegrino , Karen K. Wong 等人近期合作…

【Arduino IDE安装】Arduino IDE的简介和安装详情

目录 &#x1f31e;1. Arduino IDE概述 &#x1f31e;2. Arduino IDE安装详情 &#x1f30d;2.1 获取安装包 &#x1f30d;2.2 安装详情 &#x1f30d;2.3 配置中文 &#x1f30d;2.4 其他配置 &#x1f31e;1. Arduino IDE概述 Arduino IDE&#xff08;Integrated Deve…

Spring Boot医院管理系统:提升患者体验

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

python pass的作用

class Phone: IMEI None # 序列号 producer “ITCAST” # 厂商 def call_by_4g(self):print("4g通话")class Phone2022(Phone): face_id “10001” # 面部识别ID def call_by_5g(self):print("2022年新功能&#xff1a;5g通话")class NFCReader: nfc_ty…

​​​​​​​如何使用Hugging Face上的FacePoke工具调整照片中人的头部位置

在照片处理中&#xff0c;调整人物的头部位置可以为你带来创意无限的效果。借助Hugging Face上的FacePoke工具&#xff0c;这一操作变得前所未有的简单和高效。以下是详细步骤&#xff0c;教你如何使用FacePoke来调整照片中人的头部位置。 第一步&#xff1a;访问FacePoke工具…