一步一步写miscdevice的驱动模块

(本文使用的平台为友善tiny210SDKv2)

对于linux的驱动程序来说,主要分为三种: miscdevice、platform_device、platform_driver

这三个结构体关系:
(基类)
kobject ——————–/ \ \/ \ \device cdev driver / \ (设备驱动操作方法) \/ \ \miscdevice platform_device platform_driver (设备驱动操作方法) (设备的资源) (设备驱动)

这时,我们先不讨论这几个间的关系与驱别,对于新手来说,上手最重要!

首先我们先看看混杂项:

在Linux驱动中把无法归类的五花八门的设备定义为混杂设备(用miscdevice结构体表述)。miscdevice 共享一个主设备号MISC_MAJOR(即10),但次设备号不同。 所有的miscdevice设备形成了一个链表,对设备访问时内核根据次设备号查找对应的miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。 在内核中用struct miscdevice表示miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。miscdevice的API实现在drivers/char/misc.c中。

第二,我们再看看混杂项设备驱动的程序组织架构:

新建一个first_led.c,先可能用到的头文件都引用上吧!

#include
#include //驱动模块必需要加的个头文件
#include
#include
#include
#include
#include
#include
#include
#include .

//对应着相应机器平台的头文件
#include
#include
#include

//给自己设备驱动定义一个名字

#define DEVICE_NAME "First_led"

名字有了,但样子是怎样的呢?现在就开始定义一个”样子”!

如果一个字符设备驱动要驱动多个设备,那么它就不应该用

misc设备来实现。

通常情况下,一个字符设备都不得不在初始化的过程中进行下面的步骤:

通过alloc_chrdev_region()分配主次设备号。

使用cdev_init()和cdev_add()来以一个字符设备注册自己。

而一个misc驱动,则可以只用一个调用misc_register()

来完成这所有的步骤。(所以misc device是一种特殊的chrdev字符设备驱动)

所有的miscdevice设备形成一个链表,对设备访问时,内核根据次设备号查找

对应的miscdevice设备,然后调用其file_operations中注册的文件操作方法进行操作。

在Linux内核中,使用struct miscdevice来表示miscdevice。这个结构体的定义为:

struct miscdevice

int minor;

const char *name;

const struct file_operations *fops;

struct list_head list;

struct device *parent;

struct device *this_device;

const char *nodename;

mode_t mode;

minor是这个混杂设备的次设备号,若由系统自动配置,则可以设置为

MISC_DYNANIC_MINOR,name是设备名

为了容易理解,我们先打大概的”样子”做好。只做minor、name、fops;

定义一个myfirst_led_dev设备:

static struct miscdevice myfirst_led_dev = {
    .minor          =    MISC_DYNAMIC_MINOR,
    .name           =    DEVICE_NAME,
    .fops               =    &myfirst_led_dev_fops,
};

Minor name 都已经定义好了。那么接下来实现一下myfirst_led_dev _fops方法。

内核中关于file_operations的结构体如下:

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 (readdir) (struct file , void *, filldir_t);

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

对于LED的操作,只需要简单实现io操作就可以了,所以只实现

long (unlocked_ioctl) (struct file , unsigned int, unsigned long);

(该函数是在linux2.6.5以后才出现在设备的操作方法中的。)

函数参数为文件节点、命令、参数

static struct file_operations myfirst_led_dev_fops = {
    .owner          = THIS_MODULE,
    .unlocked_ioctl = myfirst_led_ioctl,
};

到了这里,我们就考虑一下LED的物理层面是怎样的实现了,通过开发板的引脚我们可以知道,四个LED是分别接到了GPJ2的0~3号管脚上。因此,我们定义一个数组来引用这几个管脚(当然不能像祼机那样对IO的物理地址进行操作了,是需要经过内核的内存映射得来的IO内存操作!而内核把ARM的IO管脚地址按一个线性地址进行了编排)

static int led_gpios[] = {
    S5PV210_GPJ2(0),
    S5PV210_GPJ2(1),
    S5PV210_GPJ2(2),
    S5PV210_GPJ2(3),
};
#define LED_NUM ARRAY_SIZE(led_gpios)//判断led_gpio有多少个

S5PV210_GPJ2(*)的定义如下

define S5PV210_GPJ2(_nr) (S5PV210_GPIO_J2_START + (_nr))

enum s5p_gpio_number {

S5PV210_GPIO_A0_START = 0,

……………………………..

S5PV210_GPIO_J2_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_J1),

……………………………….

#define S5PV210_GPIO_NEXT(__gpio) \

((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)

(注:##是粘贴运算,具体用法请自行找度娘或谷哥)

给用户空间的接口操作:

static long myfirst_led_ioctl(struct file *filp, unsigned int cmd,
        unsigned long arg)
{
    switch(cmd) {
        case 0:
        case 1:
            if (arg > LED_NUM) {
                return -EINVAL;//判读用户的参数是否有误
            }

            gpio_set_value(led_gpios[arg], !cmd);//用户选定的LED并设置值
            //printk(DEVICE_NAME": %d %d\n", arg, cmd);
            break;
        default:
            return -EINVAL;
    }
    return 0;
}

对于gpio_set_value(unsigned int gpio, int value),内核有以下定义:

static inline void gpio_set_value(unsigned int gpio, int value)

__gpio_set_value(gpio, value);

void __gpio_set_value(unsigned gpio, int value)

struct gpio_chip *chip;

chip = gpio_to_chip(gpio);

WARN_ON(chip->can_sleep);

trace_gpio_value(gpio, 0, value);

chip->set(chip, gpio – chip->base, value);

}//到这里我们就不再分析下去了 ,无非就是判定是哪一个芯片

程序写到这里,对于用户空间来说,已经有了完整的操作方法接口,但对于内核模块来说,还缺少驱动模块的进入与退出。以下接着写驱动模块的初始化(即进入)和退出。

static int __init myfirst_led_dev_init(void) {;}

static void __exit myfirst_led_dev_exit(void) {;}

函数如上。双下划线表示模块在内核启动和关闭时自动运行和退出

对于驱动模块的初始化函数,要写些什么呢?我们这样考虑:

对于用户空间接口来说,我们的实现函数只是给出了IO的值设置的,但是ARM的IO管脚使用还是需要配置方向、上拉下拉…..才能正常使用的,并且所有的硬件资源,都是受内核所支配的,驱动程序必需向内核申请硬件资源才能对硬件进行操作。另外还需要对设备进行注册,内核才知道你这个设备是什么东东,用到哪些东西。这些操作,我们安排在init里实现!

static int __init myfirst_led_dev_init(void)
{
    int ret;
    int i;

    for (i = 0; i < LED_NUM; i++)
  {
        ret = gpio_request(led_gpios[i], "LED");//申请IO引脚
        if (ret) {
                printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,
                    led_gpios[i], ret);
                return ret;
                }
        s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
        gpio_set_value(led_gpios[i], 1);
    }
    ret = misc_register(&myfirst_led_dev);
    printk(DEVICE_NAME"\tinitialized\n");
    return ret;
}

pio_request(unsigned gpio, const char *label)

gpio则为你要申请的哪一个管脚,label为其名字 。

int s3c_gpio_cfgpin(unsigned int pin, unsigned int config);

对芯片进行判断,并设置引脚的方向。

ret = misc_register(&myfirst_led_dev);.

该函数中、内核会自动为你的设备创建一个设备节点

对设备进行注册

到这里,设备的初始化与注册已经完成!

当用户不再需要该驱动资源时,我们必需在驱动模块中,对占用内核的资源进行主动的释放!

因此在驱动模块退出时,完成这些工作!

static void __exit myfirst_led_dev_exit(void) {
    int i;

    for (i = 0; i < LED_NUM; i++) {
        gpio_free(led_gpios[i]);
    }

    misc_deregister(&myfirst_led_dev);
}

gpio_free(led_gpios[i]);

释放IO资源

misc_deregister(&m yfirst _led_dev);

注销设备

还需要模块的初始化与退出函数声明

module_init(m yfirst _led_dev_init);

module_exit(m yfirst _led_dev_init);

最后,为了保持内核驱动模块的风格,我们还要加上相应的许可跟作者

MODULE_LICENSE(“GPL”);

MODULE_AUTHOR(“jjvip136@163.com”);

好了,程序已经打好出来了(黄色代码),我们把它整理好,试下编译一下试试效果(晚点补上效果)。

Original: https://www.cnblogs.com/snake-hand/p/3212483.html
Author: 爱生活,爱编程
Title: 一步一步写miscdevice的驱动模块

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/546977/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 关于 Delphi 中流的使用(1) 用 TMemoryStream(内存流) 入门

    所谓”流”, 就是一段数据或是一块内存; 在进行流操作时, 我们不必关心流中的数据到底是什么; 只需要知道流的大小和当前的指针位置. 所以流只有两个属性: …

    技术杂谈 2023年5月31日
    081
  • [转]钉钉宜搭

    1. 简介 2. 首页 3. 工作台 4. 普通表单 5. 流程表单 6. 报表 7. 外部链接 8. 自定义页面 9. 高级功能 1. 简介 词汇表可以帮助您了解一些特殊词汇的概…

    技术杂谈 2023年5月30日
    095
  • Launch Shopify主题模板设置修改

    Launch是Kickstarter风格的Shopify主题,旨在将初创公司变成成功的商店。展示细节并讲述您的产品故事。支持OS 2.0,适合‎健康与美容, 家庭与园艺, 运动与休…

    技术杂谈 2023年5月30日
    097
  • go-micro集成链路跟踪的方法和中间件原理

    前几天有个同学想了解下如何在go-micro中做链路跟踪,这几天正好看到wrapper这块,wrapper这个东西在某些框架中也称为中间件,里边有个opentracing的插件,正…

    技术杂谈 2023年7月11日
    099
  • 初识CityEngine【转】

    一、CityEngine历史 二、CityEngine建模思想 1、生成城市地块 2、楼层房间切割 3、建模思想、流程 (1)、建筑生成思想 (2)、官方示意流程图 三、CityE…

    技术杂谈 2023年5月31日
    084
  • 关于shape和axis的使用

    我自己对shape和axis的理解: shape表示的是维度,表示顺序是从外到内,比如一个Dataframe的形状是(4,5)那么shape[0]=4即Dataframe有4行,s…

    技术杂谈 2023年7月11日
    075
  • TCP三次握手四次挥手

    最近在恶补计算机网络方面的知识,之前对于TCP的三次握手和四次分手也是模模糊糊,对于其中的细节更是浑然不知,最近看了很多这方面的知识,也在系统的学习计算机网络,加深自己的CS功底,…

    技术杂谈 2023年7月24日
    087
  • Hbase概述与读写流程

    Hbase概述与读写流程 一、Hbase概述 Hbase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,用于存储海量的结构化或者半结构化,非结构化的数据(底层是字节数组做存…

    技术杂谈 2023年7月11日
    065
  • 计算机领域中随处可见的抽象

    想要管理多种具体的东西,那么需要遵守每种东西的规范。如果想要提供一种通用模式来对这些具体的东西统一管理,需要使用一种古老的技术:抽象。 抽象是将多种具体的东西(管理时需要遵守的规范…

    技术杂谈 2023年5月31日
    096
  • 蛋白质基础组成结构

    技术背景 了解蛋白质的基本组成单元和结构,有助于了解蛋白质的特性。对于蛋白质结构的研究,在医药领域是非常核心的重要工作。这里我们仅仅介绍一些蛋白质的基本组成单元——20种氨基酸的种…

    技术杂谈 2023年7月25日
    085
  • njoj 1251 zlly长了一张包子脸

    njoj 1251 zlly长了一张包子脸 题意: zlly长了一张包子脸。他特别喜欢吃糖果。如今他手头有若干种糖果,每种糖果有个口味值,每种糖果有无数多个。然后娄童鞋也很喜欢吃糖…

    技术杂谈 2023年5月30日
    097
  • asp.net C# 连接MySQL8.0的caching_sha2_password问题

    解决方法可用以下命令: 网上说用: 以上方法未成功,可能还是mysql 8.0密码验证机制问题导致,所以需要修改一下my.ini文件如下: 如果安装目录下找不到my.ini文件 w…

    技术杂谈 2023年6月21日
    095
  • 面向对象范式和面向过程范式的不同之处

    面向对象范式和面向过程范式的不同之处 面向过程 在面向过程的程序设计中,数据和数据上的操作是分离的,而且这种做法要求传递数据给方法。 面向过程的范式重点在于设计方法。 面向对象 面…

    技术杂谈 2023年7月23日
    066
  • 设计模式 23 访问者模式

    访问者模式(Visitor Pattern)属于 行为型模式 生活中经常会有这样的情况,同样的事物不同人有完全不同的感受,正所谓 一千个读者一千个哈姆雷特。 程序中也是一样,往往不…

    技术杂谈 2023年7月25日
    065
  • c json解析示例

    json-c是最主流的json c库。[root@hs-10-20-30-193 build]# cmake -DCMAKE_INSTALL_PREFIX=/usr/local ….

    技术杂谈 2023年6月1日
    085
  • Vue项目关闭ESLint + Prettier代码规范

    我们用Vue-Cli脚手架新建项目时会选择使用ESLint + Prettier来统一我们的前端代码风格,但这对新手使用有很大困难,严格的格式要求容易出现很多警告和错误,这时最好关…

    技术杂谈 2023年5月31日
    092
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球