Linux 字符设备驱动(新)

文章目录

  • 新字符设备驱动
    • 新字符驱动原理
      • **分配和释放设备号**
      • **新的字符设备注册方法**
    • **自动创建设备节点**
      • mdev机制
      • 创建和删除设备类
      • 创建和销毁设备
    • 设置文件私有数据
    • 新字符设备驱动LED
    • 新字符设备驱动模板

新字符设备驱动

register_chrdevunregister_chrdev 这两个函数是老版本驱动使用的函数,现在新的字符设备驱动已经不再使用这两个函数,而是使用Linux内核推荐的新字符设备驱动API函数。

新字符驱动原理

分配和释放设备号

使用 register_chrdev 函数注册字符设备的时候,会出现两个问题。①、需要我们事先确定好哪些主设备号没有使用。②、会将一个主设备号下的所有次设备号都使用掉。解决这两个问题最好的方法就是要使用设备号的时候向 Linux 内核申请。

#define MY_NAME "new_dev_test"

int major;  /*主设备号*/
int minor;  /*次设备号*/
dev_t dev;  /*设备号*/

if(major) /*定义了主设备号*/
{
    dev = MKDEV(major,0); /*次设备默认从0开始*/
    register_chrdev_region(dev,1,MY_NAME); /*静态申请设备号*/
}else
{
    alloc_chrdev_region(&dev,0,1,MY_NAME); /*动态申请设备号*/
    major = MAJOR(dev); /*主设备号*/
    minor = MINOR(dev); /*次设备号*/
}

unregister_chrdev_region(dev, 1); /*注销设备号*/

新的字符设备注册方法

linux/cdev.h 中定义了cdev设备结构体。

struct cdev {
	struct kobject kobj;
	struct module *owner;   /*默认就是THIS_MODULE*/
	const struct file_operations *ops; /*文件结构体*/
	struct list_head list;
	dev_t dev;	/*设备号*/
	unsigned int count;
};
/* dev结构体初始化函数 */
void cdev_init(struct cdev *, const struct file_operations *);
/* 向 Linux 系统添加字符设备  */
int cdev_add(struct cdev * cdev, dev_t dev, unsigned count);
// count代表添加字符设备个数
// cdev_add 和 alloc_chrdev_region 或者 register_chrdev_region实现了 register_chrdev 函数
/* cdev_del 函数从 Linux 内核中删除相应的字符设备 */
void cdev_del(struct cdev *);
// cdev_del 和 unregister_chrdev_region 这两个函数合起来的功能相当于 unregister_chrdev 函数。
  • 操作例子
struct cdev dev_test;
static struct file_operations my_fops = {
    .owner = THIS_MODULE,
    /* 其他操作函数 */
};

dev_test.owner = THIS_MODULE;
cdev_init(&dev_test, &my_fops);
cdev_add(&dev_test, dev, 1); /*添加字符设备*/

cdev_del(&dev_test);

自动创建设备节点

当我们使用 modprobe 加载驱动程序以后还需要使用命令“mknod”手动创建设备节点。在驱动中实现自动创建设备节点的功能以后,使用 modprobe 加载驱动模块成功的话就会自动在/dev目录下创建对应的设备文件。

mdev机制

udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除,udev 可以检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。比如使用modprobe 命令成功加载驱动模块以后就自动在/dev 目录下创建对应的设备节点文件,使用rmmod 命令卸载驱动模块以后就删除掉/dev 目录下的设备节点文件。使用 busybox 构建根文件系统的时候,busybox 会创建一个 udev 的简化版本—mdev。使用mdev 来实现设备节点文件的自动创建与删除,Linux 系统中的热插拔事件也由 mdev 管理。

自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。

创建和删除设备类

linux/device.h 中定义如下:

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 */
/* 创建设备类 参数 owner 一般为 THIS_MODULE,参数 name 是类名字 */
#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})

class = class_create(THIS_MODULE,MY_NAME);
class_destroy(class);

创建和销毁设备

/*
创建设备
参数 parent 是父设备,一般为 NULL,也就是没有父设备;
参数 devt 是设备号;
参数 drvdata 是设备可能会使用的一些数据,一般为 NULL;
参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件。
返回值就是创建好的设备。
*/
struct device *device_create(struct class *cls, struct device *parent,
			     dev_t devt, void *drvdata,
			     const char *fmt, ...);
/* 销毁创建的设备 */
extern void device_destroy(struct class *cls, dev_t devt);

device = device_create(class,NULL,dev,NULL,MY_NAME);
device_destroy(class,dev);
  • 操作例子
dev_t dev;  /*设备号*/
struct class *class; /*类*/
struct device *device; /*设备*/

#define MY_NAME "my_led"

/*驱动入口函数 当使用“insmod my_dev.ko”命令或“modprobe my_dev.ko”加载驱动的时候,
    mydev_init 这个函数就会被调用*/
static int __init mydev_init(void)
{
	class = class_create(THIS_MODULE,MY_NAME);
    device = device_create(class,NULL,dev,NULL,MY_NAME);
    return 0;
}

/*驱动出口函数 当使用“rmmod my_dev.ko”命令或者“modprobe -r my_dev.ko”卸载具体驱动的时候 
	mydev_exit 函数就会被调用*/
static void __exit mydev_exit(void)
{
	device_destroy(class,dev);
    class_destroy(class);
}

module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数

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

设置文件私有数据

将设备的所有属性给设置为一个结构体。编写驱动 open 函数的时候将设备结构体作为私有数据添加到设备文件中。

typedef struct my_dev{
    int major;  /*主设备号*/
	int minor;  /*次设备号*/
	dev_t dev;  /*设备号*/
	struct cdev cdev;  /* cdev */
	struct class *class; /*类*/
	struct device *device; /*设备*/
}my_dev_t;

my_dev_t my_dev;

/*打开设备*/
static int my_dev_open(struct inode *inode, struct file *file)
{
    file->private_data = &my_dev; /*设置私有数据*/
    return 0;
}

新字符设备驱动LED

#include    // module_init、module_exit函数的头文件所在地
#include      //添加文件描述符,设备注册注销头文件
#include  // 添加module_XXX宏定义头文件
#include 
#include 
#include 
#include 
#include 
#include    // 包含readl等IO操作函数
#include 
#include 
#include 
#include 
#include 

#define NEW_CHAR_CNT 1   /*设备号数量*/
#define MY_NAME "new_dev_test" /*设备名字*/

typedef struct my_dev{
    int major;  /*主设备号*/
	int minor;  /*次设备号*/
	dev_t dev;  /*设备号*/
	struct cdev cdev;  /* cdev */
	struct class *class; /*类*/
	struct device *device; /*设备*/
}my_dev_t;

my_dev_t my_new_dev;  /*新设备*/

#define LED_OFF     0   /* 关灯 */
#define LED_ON      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;   // 设置IO时钟虚拟地址
static void __iomem *SW_MUX_GPIO1_IO03; // 设置IO复用虚拟地址
static void __iomem *SW_PAD_GPIO1_IO03; // 设置IO属性虚拟地址
static void __iomem *GPIO1_DR;          // 设置IO输出功能虚拟地址
static void __iomem *GPIO1_GDIR;        // led虚拟地址

void led_switch(u8 sta)
{
    u32 val = 0;
    if(sta == LED_ON)  /*开灯*/
    {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3); 
        writel(val, GPIO1_DR);
    }else if(sta == LED_OFF)  /*关灯*/
    {
        val = readl(GPIO1_DR);
        val|= (1 << 3);
        writel(val, GPIO1_DR);
    } 
}

/*打开设备*/
static int my_dev_open (struct inode *inode, struct file *file)
{
    file->private_data = &my_new_dev; /*设置私有数据*/
    return 0;
}
/*读设备*/
static ssize_t my_dev_read (struct file *file, char __user *buf, 
                            size_t cnt, loff_t * offt)
{
    return 0;
}
/*写设备*/
static ssize_t my_dev_write (struct file *file, const char __user *buf,
                         size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char data_buf[1];
    unsigned char led_stat;
    /*接收用户空间的数据*/
    ret = copy_from_user(data_buf,buf,cnt);
    if(ret < 0)
    {
        printk(KERN_INFO "write fail ...\n");
        return -EFAULT; 
    }

    led_stat = data_buf[0]; /* 获取状态值 */

    if(led_stat == LED_ON)
    {
        led_switch(LED_ON);
    }else if(led_stat == LED_OFF)
    {
        led_switch(LED_OFF);
    }
    return 0;
}
/*释放设备*/
static int my_dev_close (struct inode *inode, struct file *file)
{
    return 0;
}

static struct file_operations my_fops = {
    .owner		= THIS_MODULE,		// 惯例,直接写即可
	.open		= my_dev_open,		// 打开设备
    .read       = my_dev_read,		// 读设备
    .write		= my_dev_write,		// 写设备
	.release	= my_dev_close,		// 关闭设备
};

/*驱动入口函数 */
static int __init mydev_init(void)
{
    u32 val = 0;

    /* 初始化 LED */
    /* 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);

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

    /*分配设备号*/
    if(my_new_dev.major) /*定义了主设备号*/
    {
        my_new_dev.dev = MKDEV(my_new_dev.major,0); /*次设备默认从0开始*/
        register_chrdev_region(my_new_dev.dev,NEW_CHAR_CNT,MY_NAME); /*静态申请设备号*/
    }else
    {
        alloc_chrdev_region(&my_new_dev.dev,0,NEW_CHAR_CNT,MY_NAME); /*动态申请设备号*/
        my_new_dev.major = MAJOR(my_new_dev.dev); /*主设备号*/
        my_new_dev.minor = MINOR(my_new_dev.dev); /*次设备号*/
    }
    /*注册设备号*/
    cdev_init(&my_new_dev.cdev,&my_fops); /*初始化cdev*/
    cdev_add(&my_new_dev.cdev,my_new_dev.dev,NEW_CHAR_CNT); /*添加一个dev*/
    /*创建类*/
	my_new_dev.class = class_create(THIS_MODULE,MY_NAME);
    if(IS_ERR(my_new_dev.class))
    {
        return PTR_ERR(my_new_dev.class);
    }
    /*创建设备*/
    my_new_dev.device = device_create(my_new_dev.class,NULL,my_new_dev.dev,NULL,MY_NAME);
    if(IS_ERR(my_new_dev.device))
    {
        return PTR_ERR(my_new_dev.device);
    }

    return 0;
}

/*驱动出口函数 */
static void __exit mydev_exit(void)
{
    /* 取消映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    /*注销字符设备*/
    cdev_del(&my_new_dev.cdev);
    unregister_chrdev_region(my_new_dev.dev, NEW_CHAR_CNT);

    /*注销掉类和设备*/
	device_destroy(my_new_dev.class,my_new_dev.dev);
    class_destroy(my_new_dev.class);
}

module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数

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

新字符设备驱动模板

#include    // module_init、module_exit函数的头文件所在地
#include      //添加文件描述符,设备注册注销头文件
#include  // 添加module_XXX宏定义头文件
#include 
#include 
#include 
#include 
#include 
#include    // 包含readl等IO操作函数
#include 
#include 
#include 
#include 
#include 

#define NEW_CHAR_CNT 1   /*设备号数量*/
#define MY_NAME "new_dev_test" /*设备名字*/

typedef struct my_dev{
    int major;  /*主设备号*/
	int minor;  /*次设备号*/
	dev_t dev;  /*设备号*/
	struct cdev cdev;  /* cdev */
	struct class *class; /*类*/
	struct device *device; /*设备*/
}my_dev_t;

my_dev_t my_new_dev;  /*新设备*/

/*打开设备*/
static int my_dev_open (struct inode *inode, struct file *file)
{
    file->private_data = &my_new_dev; /*设置私有数据*/
    return 0;
}
/*读设备*/
static ssize_t my_dev_read (struct file *file, char __user *buf, 
                            size_t cnt, loff_t * offt)
{
    return 0;
}
/*写设备*/
static ssize_t my_dev_write (struct file *file, const char __user *buf,
                         size_t cnt, loff_t *offt)
{
    return 0;
}
/*释放设备*/
static int my_dev_close (struct inode *inode, struct file *file)
{
    return 0;
}

static struct file_operations my_fops = {
    .owner		= THIS_MODULE,		// 惯例,直接写即可
	.open		= my_dev_open,		// 打开设备
    .read       = my_dev_read,		// 读设备
    .write		= my_dev_write,		// 写设备
	.release	= my_dev_close,		// 关闭设备
};

/*驱动入口函数 */
static int __init mydev_init(void)
{
    /*分配设备号*/
    if(my_new_dev.major) /*定义了主设备号*/
    {
        my_new_dev.dev = MKDEV(my_new_dev.major,0); /*次设备默认从0开始*/
        register_chrdev_region(my_new_dev.dev,NEW_CHAR_CNT,MY_NAME); /*静态申请设备号*/
    }else
    {
        alloc_chrdev_region(&my_new_dev.dev,0,NEW_CHAR_CNT,MY_NAME); /*动态申请设备号*/
        my_new_dev.major = MAJOR(my_new_dev.dev); /*主设备号*/
        my_new_dev.minor = MINOR(my_new_dev.dev); /*次设备号*/
    }
    /*注册设备号*/
    cdev_init(&my_new_dev.cdev,&my_fops); /*初始化cdev*/
    cdev_add(&my_new_dev.cdev,my_new_dev.dev,NEW_CHAR_CNT); /*添加一个dev*/
    /*创建类*/
	my_new_dev.class = class_create(THIS_MODULE,MY_NAME);
    if(IS_ERR(my_new_dev.class))
    {
        return PTR_ERR(my_new_dev.class);
    }
    /*创建设备*/
    my_new_dev.device = device_create(my_new_dev.class,NULL,my_new_dev.dev,NULL,MY_NAME);
    if(IS_ERR(my_new_dev.device))
    {
        return PTR_ERR(my_new_dev.device);
    }

    return 0;
}

/*驱动出口函数 */
static void __exit mydev_exit(void)
{
    /*注销字符设备*/
    cdev_del(&my_new_dev.cdev);
    unregister_chrdev_region(my_new_dev.dev, NEW_CHAR_CNT);

    /*注销掉类和设备*/
	device_destroy(my_new_dev.class,my_new_dev.dev);
    class_destroy(my_new_dev.class);
}

module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数

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

你可能感兴趣的:(Linux,RAM,linux,运维,服务器)