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


系列文章:
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明灭。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值