Linux驱动开发(二)

news2024/11/14 20:02:03

一、驱动流程

驱动需要以下几个步骤才能完成对硬件的访问和操作:

  1. 模块加载函数 module_init
  2. 注册主次设备号 <应用程序通过设备号找到设备>
  3. 驱动设备文件 <应用程序访问驱动的方式> 1、手动创建 (mknod)2、程序自动创建
  4. file_operations <驱动对硬件的读、写、释放等>
  5. 模块卸载函数 module_exit

在这里插入图片描述

二、举例详解

1、第一种向内核注册字符设备

#include <linux/module.h> // module_init module_exit
#include <linux/init.h>    // __init __exit
#include <linux/fs.h>

#define MYMAJOR 200
#define MYNAME    "LED_DEVICE"

 //int (*open) (struct inode *, struct file *);

//open函数的格式是上面的格式:

static int led_dev_open(struct inode *inode, struct file *file)

{
  printk(KERN_INFO "led_dev_open open\n");
}

//release函数的原型是:int (*release) (struct inode *, struct file *);

static int led_dev_close(struct inode *inode, struct file *file)
{
  printk(KERN_INFO "led_dev_close close\n");
}

static const struct file_operations led_dev_fops{
  .opne = led_dev_open,
  .release = led_dev_close,
}

static int __init leddev_init(void)
{
  int ret = -1;
  printk(KERN_INFO "leddev_init");
 
  ret = register_chrdev(MYMAJOR, MYNAME, &led_dev_fops);
  if(ret) {
    printk(KERN_ERR "led devices rigister failed");
    retunt -EINVAL;
  }

  printk(KERN_INFO "led regist sucess");
  return 0;
}

static int __exit leddev_exit(void)
{
  printfk(KERN_INFO "led device exit");
  unregister_chrdev(MYMAJOR, NAME)}

module_init(leddev_init);module_exit(leddev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("bhc");				// 描述模块的作者
MODULE_DESCRIPTION("led test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息

注:
通过对驱动的流程进行分析,以上代码中缺少对设备节点的创建,也就是说,上边的代码,应用程序是没有方法进行访问和操作的,这时,我们可以通过手动的方式进行处理,即使用mknod进行创建,

应用调用驱动是通过驱动设备文件来调用驱动的,我们首先要用mknod /dev/xxx c 主设备号 次设备号 命令来创建驱动设备文件


安装好驱动以后,主设备号可以在/proc/devices文件中查看,但是由于不同的设备主设备号占用的不一样,有时候需要系统来自动分配

主设备号,这个如何实现呢:

我们可以在register_chrdev函数的major变量传参0进去,因为这个函数的返回值为主设备号,所以我们定义一个全局变量来接受这个值即可

static int mymajor;

//注册的时候

mymajor = register_chrdev(0, MYNAME, &ded_dev_fops); # 返回的是自动分配的主设备号

//释放的时候

unregister_chrdev(mymajor, MYNAME);

这样即可;

register_chrdev(major, name, struct file_openrations) # 注册设备号,缺点是只能注册主设备号
unregister_chrdev(major, name) # 注销设备号

1、第二种向内核注册字符设备

#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <plat/map-base.h>
#include <plat/map-s5p.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/ioport.h>
#include <linux/string.h>
#include <asm/io.h>
#include <linux/cdev.h>

//#define MYMAJOR            200
//#define MYNAME            "LED_DEVICE"
#define MYDEV             250
#define LED_COUNT        1

#define GPJ0_PA_base        0xE0200240        
#define GPJ0CON_PA_OFFSET    0x0

struct cdev my_led_cdev;

unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;

static char kbuf[100];
static int mymojor;

static int led_dev_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led_dev open\n");
    
    return 0;
}

static int led_dev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led_dev close\n");
    
    return 0;
}

ssize_t led_dev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    int ret = -1;
        
    ret = copy_to_user(buf, kbuf, sizeof(kbuf));
    if(ret) {
        printk(KERN_ERR "kernel led read error\n");
    }
    printk(KERN_INFO "led device read success\n");
}

static ssize_t led_dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
    int ret = -1;
    
    //首先把kbuf清零
    memset(kbuf, 0, sizeof(kbuf));    
    ret = copy_from_user(kbuf, user_buf, count);
    if(ret) {
        printk(KERN_ERR "kernel led write error\n");
        return -EINVAL;
    }
 
    printk(KERN_INFO "led device write success\n");
    
    if (kbuf[0] == '1') {
        *pGPJ0CON = 0x11111111;
        *(pGPJ0CON + 1) = ((0<<3) | (0<<4) | (0<<5));
    }
    
    if (kbuf[0] == '0') {
        *(pGPJ0CON + 1) = ((1<<3) | (1<<4) | (1<<5));
    }  
    return 0;
}

static const struct file_operations led_dev_fops = {
    .open = led_dev_open,
    .write = led_dev_write,
    .read = led_dev_read,    
    .release = led_dev_release,
    .owner = THIS_MODULE,
};

// 模块安装函数
static int __init leddev_init(void)
{    
    int err = 0;
    printk(KERN_INFO "led_device init\n");
    
    
    //在这里进行注册驱动,因为安装驱动实际上执行的就是这个函数;
    err = register_chrdev_region(MKDEV(MYDEV,0), LED_COUNT, "MY_LED_DEV");
    
    if(err)
    {
        printk(KERN_ERR " register_chrdev_region failed\n");
        
        return -EINVAL;
    }
    
    printk(KERN_INFO "leddev_dev regist success\n");
    
    cdev_init(&my_led_cdev, &led_dev_fops);
    
    cdev_add(&my_led_cdev, MKDEV(MYDEV,0), LED_COUNT);
    
    
    if(!request_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8, "GPJ0PABAST")) {
        return -EINVAL;
    }
    
    pGPJ0CON = ioremap(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 4);
        
    return 0;
}

// 模块下载函数
static void __exit leddev_exit(void)
{
    printk(KERN_INFO "leddev_dev  exit\n");
    
    //注销led设备驱动
    cdev_del(&my_led_cdev);
    
    unregister_chrdev_region(MKDEV(MYDEV,0), LED_COUNT);
    
    iounmap(GPJ0_PA_base + GPJ0CON_PA_OFFSET);
    
    release_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8);
    
    printk(KERN_INFO "leddev_dev  unregist success\n");   
}

module_init(leddev_init);
module_exit(leddev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("bhc");                 // 描述模块的作者
MODULE_DESCRIPTION("led test");       // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息

注:

(1)第二种注册函数

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

以上是第一种注册字符设备的方式,这种方式无法设置次设备号。本次的设备如下所示:

int register_chrdev_region(dev_t from, unsigned count, const char *name) # 注册设备
void cdev_init(struct cdev *, const struct file_operations *);  # 初始化函数
int cdev_add(struct cdev *p, dev_t dev, unsigned count)  # 添加设备函数
void cdev_del(struct cdev *p)   # 删除设备函数
void unregister_chrdev_region(dev_t from, unsigned count)  # 卸载设备

(2)主次设备号注册

linux内核为我们提供了三个宏来确定from、主设备号、次设备号

MKDEV、MAJOR、NIMOR,这么是这三个宏在linux内核中定义的用法:

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

比如说我们的主设备号为250 第一个次设备号为0,那么from应该赋值的参数为MKDEV(250, 0)

知道dev的话 主设备号为 MAJOR(dev)、次设备号为:MINOR(dev)


(3)内核和驱动之间的数据交互

static inline long copy_to_user(void __user *to, const void *from, unsigned long n);     #  从驱动到用户空间
static inline long copy_from_user(void *to, const void __user * from, unsigned long n)   #  从用户空间到驱动

(4)虚拟空间映射

下面我们的低层驱动开始真正的操作硬件了:

在操作硬件的时候,我们会用到硬件的寄存器,因为我们之前在逻辑程序中使用的物理地址来直接写的,而我们在开发板上移植好内核以后

我们的内核程序就是运行在虚拟地址上了,所以我们要看一下我们的linux内核中虚拟地址跟物理地址是如何映射的

动态虚拟地址:当我们要使用这个寄存器的物理地址的时候,不用事先建立好的页表,而是给物理地址动态的分配一个虚拟地址,操作的时候直接使用这个动态分配的虚拟地址

操作物理地址即可,使用完以后取消映射即可;

使用动态虚拟地址映射首先:

1:建立映射

使用request_mem_region向内核申请虚拟地址空间;

#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)

request_mem_region实际上是一个宏,真正调用的是 __request_region这个函数;

request_mem_region宏需要三个参数:start:启示的物理地址,n长度,name

申请成功则返回0;

ioremap

#define ioremap(cookie,size) __arm_ioremap(cookie, size, MT_DEVICE)

也是一个宏,调用的是内核函数__arm_ioremap

这个宏需要两个参数起始物理地址以及 长度;

2:使用完以后我们首先要消除映射

取消映射iounmap宏

#define iounmap(cookie) __iounmap(cookie)

接受一个参数,起始物理地址;

然后在消除分配的虚拟地址

使用release_mem_region

#define release_mem_region(start,n) __release_region(&iomem_resource, (start), (n))

这个宏只需要两个参数即可一个是起始物理地址,一个长度;

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

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

相关文章

Synchronized 原理

基本特点(只考虑 JDK 1.8): 1. 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁.2. 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁.3. 实现轻量级锁的时候大概率用到的自旋锁策略4. 是一种不公平锁5. 是一种可重入锁6. 不是读写锁 加锁工作过程 JVM 将 s…

【Kafka】【三】安装Kafka服务器

Kafka基本知识 Kafka介绍 Kafka是最初由Linkedin公司开发&#xff0c;是⼀个分布式、⽀持分区的&#xff08;partition&#xff09;、多副本的 &#xff08;replica&#xff09;&#xff0c;基于zookeeper协调的分布式消息系统&#xff0c;它的最⼤的特性就是可以实时的处理 …

蓝牙安全(AES-CCM)

目录 AES-CCM CCM规范加密过程 CCM规范解密认证过程 formatting函数 counter generation函数 蓝牙AES-CCM加密流程 参考文献 AES-CCM Advanced Encryption Standard-Counter with Cipher Block Chaining-Message Authentication Code 自蓝牙4.1起蓝牙的加密算法开始采…

RabbitMQ-其他问题

一、幂等性问题&#xff1a;消费者在消费MQ中的消息时&#xff0c;MQ已把消息发送给消费者&#xff0c;消费者在给MQ返回ACK时网络中断&#xff0c;故MQ未收到确认消息&#xff0c;该消息会重新发送给其他消费者&#xff0c;或者在网络重连后再次发送给消费者&#xff0c;但实际…

第三章虚拟机的克隆,快照,迁移删除

1.虚拟机的克隆 如果你已经安装了一台linux操作系统&#xff0c;你还想再更多的&#xff0c;没有必要再重新安装&#xff0c;你只需要克 隆就可以&#xff0c;看演示。 方式1&#xff0c;直接拷贝一份安装好的虚拟机文件,再用虚拟机打开这个文件方式2&#xff0c;使用vmware的…

企业三要素核验API接口,你了解多少?

企业三要素核验API接口是指哪些要素&#xff1f;企业三要素是一种有关企业实名认证的应用程序接口也称API&#xff0c;企业的名称、统一社会信用代码和法人代表姓名统称企业三要素。企业三要素核验API接口的资源来自国家工商总局数据库&#xff0c;通过数据库资料三个要素进行核…

字母板上的路径 题解,力扣官方出来挨打(小声)

字母板上的路径 我们从一块字母板上的位置 (0, 0) 出发&#xff0c;该坐标对应的字符为 board[0][0]。 在本题里&#xff0c;字母板为board [“abcde”, “fghij”, “klmno”, “pqrst”, “uvwxy”, “z”]&#xff0c;如下所示。 我们可以按下面的指令规则行动&#xff1a…

Dubbo基本原理和用法讲解

Dubbo基本原理和用法讲解 序言&#xff1a;学习一项新技术&#xff0c;一般从是什么、为什么、怎么用三个方面进行学习。本篇文章也不例外&#xff0c;笔者将从Dubbo是什么&#xff1f;、为什么会产生Dubbo技术&#xff1f;、如何在项目中使用Dubbo技术。最后&#xff0c;笔者…

基于springboot+vue的宠物商城系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

Fluent Python 笔记 第 6 章 使用一等函数实现设计模式

虽然设计模式与语言无关&#xff0c;但这并不意味着每一个模式都能在每一门语言中使用。1996 年&#xff0c;Peter Norvig 在题为“Design Patterns in Dynamic Languages”(http://norvig.com/design- patterns/)的演讲中指出&#xff0c;Gamma 等人合著的《设计模式:可复用面…

序列化和反序列化~如何实现自定义协议?jsoncpp的使用

目录 序列化反序列化的概念 为什么要进行序列化和反序列化&#xff1f; 自定义协议实现业务 jsoncpp实现序列化和反序列化 序列化&#xff1a; 反序列化&#xff1a; 自定义协议jsoncpp实现简易计算器服务整体逻辑 Server.cc Client.cc 运行结果 序列化反序列化的概念…

重建control遗漏数据文件,reseltogs报ORA-1555错误处理----惜分飞

又一客户,误删除oracle redo导致数据库无法正常启动,自己尝试重建ctl,结果遗漏部分oracle数据文件并且尝试过resetlogs,导致部分文件resetlogs scn不一致.导致重建ctl失败Fri Feb 10 12:41:20 2023CREATE CONTROLFILE REUSE DATABASE "orcl"RESETLOGS NOARCHIVELOG M…

GO 中的 init 函数

前言 go 语言中有一个非常神奇的函数 init ,它可以在所有程序执行开始前被执行&#xff0c;并且每个 package 下面可以存在多个 init 函数&#xff0c;我们一起来看看这个奇怪的 init 函数。 init 特性 init 函数在 main 函数之前执行&#xff0c;并且是自动执行&#xff1b…

Docker网络实现原理

目录 1 Docker网络实现原理 1.2 为容器创建端口映射 方法一&#xff1a;随机映射端口&#xff08;从32768开始&#xff09; 方法二&#xff1a;指定映射端口 1.3 查看容器的输出和日志信息 二 Docker的网络模式 2.1 Docker的网络模式&#xff08;41&#xff09; 2.2 查看…

【大数据趋势】2月12日 货币发动机牵着港股和A股走,历史不会简单重演,但是会用类似的方法让人再踏入同一条河流

行情核心源头之一 : 离岸人民币和美元趋势历史对比&#xff0c;预示着一个阶段底部正在形成中 历史总是很容易忘记&#xff0c;应该很少有人记得18年发生了什么。还是让大数据程序来对比一下。【红色标记1】RSI预示着價格強度的动能情况&#xff0c;同样是达到了一个高点&…

2021 WAIC 世界人工智能大会参会总结

前言 2021 年世界人工智能大会&#xff08;WAIC&#xff09;于2021年7月7日至10日在上海世博展览馆举办&#xff0c;本届大会继续秉持「智联世界」的理念&#xff0c;以「众智成城」为主题&#xff0c;促进全球人工智能创新思想、技术、应用、人才和资本的集聚和交流&#xff…

JS逆向案例分享----prototype的妙用

方向不对&#xff0c;努力白费。爬虫也是如此。今天分享的案例很能说明问题。目标&#xff1a;某集团公司采购信息aHR0cHM6Ly9lYy5taW5tZXRhbHMuY29tLmNuL29wZW4vaG9tZS9wdXJjaGFzZS1pbmZv浏览器抓包&#xff0c;请求载荷里就一个加密参数param全局搜索“param:”&#xff0c;有…

【计组】DMA、数据完整性--《深入浅出计算机组成原理》(十三)

目录 一、DMA &#xff08;一&#xff09;理解DMA&#xff0c;一个协处理器 &#xff08;二&#xff09; Kafka 的实现原理 二、数据的完整性 &#xff08;一&#xff09;单比特翻转&#xff1a;软件解决不了的硬件错误 &#xff08;二&#xff09;海明码 1、海明码的纠错…

《狂飙》壁纸大嫂如此惊艳,做成日历壁纸天天看(7)

小朋友们好&#xff0c;大朋友们好&#xff01;我是猫妹&#xff01;话说兔年春节期间&#xff0c;一部反黑反腐电视剧横空出世&#xff0c;收视率和口碑都有不错的成绩&#xff01;这部电视剧叫《狂飙》&#xff01;你看了吗&#xff1f;我没看&#xff01;不过这丝毫不影响它…

C语言fread和fwrite的用法详解

fgets() 有局限性&#xff0c;每次最多只能从文件中读取一行内容&#xff0c;因为 fgets() 遇到换行符就结束读取。如果希望读取多行内容&#xff0c;需要使用 fread() 函数&#xff1b;相应地写入函数为 fwrite()。对于 Windows 系统&#xff0c;使用 fread() 和 fwrite() 时应…