方法二:新字符设备驱动程序步骤和应用程序验证(LED灯驱动)(linux2.6)(简单总结)

〇、新字符设备驱动(linux2.6)的方法与老版本(Linux 2.4及之前版本)对比

1. 设备号的获取方式

老版本(如Linux 2.4及之前)

  • 开发者需要自行指定主设备号。这可能导致设备号冲突,尤其是当多个驱动试图使用相同的主设备号时。

新版本(Linux 2.6及之后)

  • 引入了动态分配设备号的功能。开发者可以通过alloc_chrdev_region()函数请求内核动态分配主设备号和次设备号,避免了设备号冲突的问题。
  • 同时,也保留了静态申请设备号的方式,即使用register_chrdev_region()函数,但这种方式需要开发者自行确保设备号不会与现有设备冲突。

2. 字符设备的注册方式

老版本(如Linux 2.4及之前)

  • 通过register_chrdev()函数一步完成设备号的分配和字符设备驱动的注册。这种方式简单直接,但不够灵活,且容易占用过多不必要的次设备号。

新版本(Linux 2.6及之后)

  • 将设备号的分配和字符设备驱动的注册分开处理。首先,通过alloc_chrdev_region()register_chrdev_region()函数获取设备号;然后,使用cdev_init()函数初始化cdev结构体,并建立cdev与文件操作集合file_operations之间的连接;最后,通过cdev_add()函数将cdev结构体添加到系统中,完成字符设备的注册。这种方式更加灵活,允许开发者根据需要动态添加或删除字符设备。

3. 设备结点文件的生成方式

老版本(如Linux 2.4及之前)

  • 通常需要手动使用mknod命令创建设备结点文件。这种方式繁琐且容易出错,尤其是在设备数量较多的情况下。

新版本(Linux 2.6及之后)

  • 引入了udev(或mdev,在嵌入式系统中常见)机制,可以自动为字符设备创建设备结点文件。开发者只需在驱动中通过class_create()device_create()函数为设备创建一个类和设备,udev(或mdev)就会根据这些信息自动生成相应的设备结点文件。这种方式大大简化了设备结点文件的创建过程,提高了系统的可维护性。

总结        

        Linux 2.6内核中的新字符设备驱动方法与老版本相比,在设备号的获取、字符设备的注册以及设备结点文件的生成方式上都有了显著的改进。这些改进使得字符设备驱动的开发更加灵活、高效和易于维护。

一、程序框架及大致步骤

一、IO初始化

对自己所使用的IO初始化。

二、申请设备号

如果没有指定设备号的话就使用如下函数来申请设备号:

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)

相关函数及结构体

1.#define MINORBITS 20

2.#define MINORMASK ((1U << MINORBITS) - 1)

3.#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))

4.#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

5.#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

第 1行,宏 MINORBITS 表示次设备号位数,一共是 20 位。

第 2 行,宏 MINORMASK 表示次设备号掩码。

第 3 行,宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。

第 4 行,宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。

第 5 行,宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备。

三、添加字符设备

cdev结构体相关结构体及相关函数

1.struct cdev {
                struct kobject kobj ;
                struct module * owner ;
                const struct file_operations * ops ;
                struct list_head list ;
                dev_t  dev ;
                unsigned int count ;
};
2.void cdev_init(struct cdev *cdev, const struct file_operations *fops)
3.int cdev_add(struct cdev *p, dev_t dev, unsigned count)
        1.在 cdev 中有两个重要的成员变量: ops dev ,这两个就是字符设备文件操作函数集合
file_operations 以及设备号 dev_t
        2.参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合
        3. 参数 p 指向要添加的字符设备 (cdev 结构体变量 ) ,参数 dev 就是设备所使用的设备号,参
count 是要添加的设备数量

  四、自动创建设备节点

              作用是代替在加载驱动程序时手动添加mknod的命令

1 struct class *class_create (struct module *owner, const char *name)
2.void class_destroy(struct class *cls);
3. struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char  *fmt, ...)
4.void device_destroy(struct class *class, dev_t devt)
        1. class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE ,参数 name 是类名字。
返回值是个指向结构体 class 的指针,也就是创建的类。
        2.class_destroy参数 cls 就是要删除的类。
        3. device_create 是个可变参数函数,参数 class 就是设备要创建哪个类下面;参数 parent 是父
设备,一般为 NULL ,也就是没有父设备;参数 devt 是设备号;参数 drvdata 是设备可能会使用
的一些数据,一般为 NULL ;参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成 /dev/xxx
这个设备文件。返回值就是创建好的设备。
        4.device_destroy参数 class 是要删除的设备所处的类,参数 devt 是要删除的设备号。

二、代码

/*************************************************************************
#    > File Name: LedDrive01.c
#    > Author: HENG-W
#    > Mail: wheng9527@foxmail.com
#    > Created Time: 2024年09月02日 星期一 11时56分45秒
#    > Describe: 新字符设备实验
#*************************************************************************/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define DEV_NAME "LedDrive01"   /*设备名 */
#define DEV_COUNT 1             /*设备号数量*/

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


void led_switch(u8 sta)
{
	u32 value = 0;
	if(sta == LEDON) {
		value = readl(GPIO1_DR);
		value &= ~(1 << 3);	
		writel(value, GPIO1_DR);
	}else if(sta == LEDOFF) {
		value = readl(GPIO1_DR);
		value|= (1 << 3);	
		writel(value, GPIO1_DR);
	}	
}

static int MyLedDev_open(struct inode *inodep, struct file *filep)
{
	printk("MyLedDev_open!\r\n");
	return 0;
}

static ssize_t MyLedDev_write (struct file *filep, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	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);		/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);		/* 关闭LED灯 */
	}
	return 0;
}
static int MyLedDev_release(struct inode *inodep, struct file *filep)
{
	printk("MyLedDev_release!\r\n");
	return 0;
}

/*自定义的led设备结构体*/
struct newledDrive_dev
{
    struct cdev cdev;       /*字符设备结构*/   
    struct class *class;    /*类指针*/
    struct device *device;  /*设备指针*/
    dev_t devid;            /*设备号*/
    int major;              /*主设备号*/
    int minor;              /*次设备号*/
};
/*设备操作函数*/
static const struct file_operations newledDrive_dev_fops = {
	.owner = THIS_MODULE,
	.open = MyLedDev_open,
    .write = MyLedDev_write,
    .release = MyLedDev_release,
};
struct newledDrive_dev newledDrive;

static int __init leddrive01_init(void)
{
    int ret = 0;
    unsigned int value =0;
    /*1.初始化led */
    /*1.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);

	/* 1.2、使能 GPIO1 时钟 */
	value = readl(IMX6U_CCM_CCGR1);
	value &= ~(3 << 26); 	/* 清除以前的设置 */
	value |= (3 << 26); 	/* 设置新值 */
	writel(value, IMX6U_CCM_CCGR1);

	/* 1.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);

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

	/* 1.5、默认关闭LED */
	value = readl(GPIO1_DR);
	value |= (1 << 3);	
	writel(value, GPIO1_DR);

    /*2.申请设备号*/
    /*register_chrdev_region(dev_t 设备号, unsigned 申请个数, const char *设备名)*/
    /*alloc_chrdev_region(主设备号, unsigned 起始次设备号, unsigned 申请个数, char *设备名)*/
    if (newledDrive.major)/* 定义了主设备号 */
    {
        newledDrive.devid = MKDEV(newledDrive.major,0);
        ret = register_chrdev_region(newledDrive.devid,DEV_COUNT,DEV_NAME);
    }else{/* 没有定义设备号 */
        ret = alloc_chrdev_region(&newledDrive.devid,0,DEV_COUNT,DEV_NAME);/* 申请设备号 */
        newledDrive.major = MAJOR(newledDrive.devid);/*主设备号*/
        newledDrive.minor = MINOR(newledDrive.devid);/*次设备号*/
    }
    if (ret < 0)
    {
        printk("申请设备号失败!!!\n");
        unregister_chrdev_region(newledDrive.devid, 1);
        return -1;
    }
    printk("LedDrive01 major=%d, minor=%d\r\n", newledDrive.major, newledDrive.minor);
    /*3.初始化字符设备、添加字符设备*/
    /*cdev_init(struct cdev *核心结构体, const struct file_operations *文件操作集合)*/
    /*cdev_add(struct cdev * 核心结构体, dev_t 设备号, unsigned 设备号的数量)*/
    newledDrive.cdev.owner = THIS_MODULE;
    cdev_init(&newledDrive.cdev,&newledDrive_dev_fops);
    ret = cdev_add(&newledDrive.cdev,newledDrive.devid,DEV_COUNT);
    if (ret<0)
    {
        printk("添加字符设备失败!!!\n");
        cdev_del(&newledDrive.cdev);
        return -1;
    }
    /*4. 自动创建设备节点、替换调在使用驱动程序mknod的手动命令*/
    /*class_create(THIS_MODULE, 名字)*/
    newledDrive.class =  class_create(THIS_MODULE,DEV_NAME);
    if (IS_ERR(newledDrive.class)) {
        ret = PTR_ERR(newledDrive.class);
	    printk("class_create error\n");
        class_destroy(newledDrive.class);
        return -1;

    }
    /*5.创建设备节点 */
    /*device_create(struct class *类结构体,struct device *父设备号可以为NULL,dev_t 设备号,void *驱动数据一般为NULL,const char *可变参数,用来生成/dev/目录下的设备文件名)*/
    newledDrive.device = device_create(newledDrive.class, NULL, newledDrive.devid, NULL, DEV_NAME);
    if (IS_ERR(newledDrive.device)) {
        ret = PTR_ERR(newledDrive.device);
        printk("device_create error\n");
        device_destroy(newledDrive.class, newledDrive.devid);       
        return -1;
    }
    printk("驱动加载驱动函数完成!!!\r\n");
    return 0;
}

static void __exit leddrive01_exit(void)
{
    /*1.删除设备*/
    device_destroy(newledDrive.class, newledDrive.devid);
    /*2.删除class类 */
    class_destroy(newledDrive.class);
    /*3.删除字符设备 */
    cdev_del(&newledDrive.cdev);
    /*4.注销设备号 */
    unregister_chrdev_region(newledDrive.devid, 1); 
    /*5. 关闭寄存器映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);
    printk("驱动卸载驱动函数完成!!!XXXXXX\r\n");
}

module_init(leddrive01_init);
module_exit(leddrive01_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("HENG-W"); //添加模块作者信息

三、效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值