十八、使用class分类管理设备

设备的大管家class

  • 使用class对硬件设备进行分类管理
  • class与用户空间守护进程udev/mdev协作,自动创建设备文件
设备驱动模型框图
  1. Linux内核启动的过程中会调用classes_init()函数在sysfs文件系统中创建一个名为class的文件夹。
  2. 我们在驱动中调用class_create()函数,在class文件夹下创建一个指定名称的分类文件夹(比如指定分类名为xxx)。
  3. 我们继续在驱动中调用device_create()函数,在指定的分类中又创建一个表示设备的文件夹(比如指定设备名为yyy)。调用这个函数的时候,我们会传入设备号,所以这个设备文件夹下会生成一个名为dev的设备属性文件存放设备号。
  4. device_create()函数除了创建/sys/class/xxx/yyy这个设备目录,还会调用kobject_uevent()函数给用户空间的udev/mdev守护进程发送一个新增设备的消息。udev/mdev收到这个消息后,会去读取/sys/class/xxx/yyy/dev属性文件中的设备号,然后调用mknod()函数在/dev目录下创建一个名为yyy的设备节点。
相关函数
创建一个class
class_create宏

include/linux/device.h

c 复制代码
#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,
			     struct lock_class_key *key);
  • owner:一般设置为THIS_MODULE
  • name:kobject对象的名字,也即是目录的名称
  • struct class里面间接继承了kobject对象
在class下添加kobject对象
device_create()函数

include/linux/device.h

c 复制代码
struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...);
  • class:新构建的class
  • parent:新kobject对象的上一层节点,一般为NULL
  • dev_t:属性文件记录该设备号
  • drvdata:私有数据,一般为NULL
  • fmt:变参参数,一般用来设置kobject对象的名字
示例程序

class_dev.c

c 复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
// 提供struct cdev结构体定义
#include <linux/cdev.h>
// 提供dev_t定义
#include <linux/types.h>
// 提供struct file_operations定义
#include <linux/fs.h>
// 提供ioremap相关定义
#include <asm/io.h>
// 提供字符串比较函数
#include <linux/string.h>
// 提供copy_from_user函数
#include <asm/uaccess.h>
// 提供sysfs_streq函数
#include <linux/device.h>

/* 原理图信息
LED:  GPIO1_IO03, Low-Triggerd
BEEP: GPIO5_IO01, Low-Triggerd
*/

/* 寄存器设置
开启GPIO时钟:
LED:  CCGR1 0x020C_406C [CG13]=b[27:26]=0b_11, GPIO1_CLK_ENABLE
BEEP: CCGR1 0x020C_406C [CG15]=b[31:30]=0b_11, GPIO5_CLK_ENABLE

0b_00 - 所有模式下关闭时钟
0b_01 - 仅在运行模式(Run mode)下打开
0b_10 - 保留
0b_11 - 在除了停止模式(Stop mode)外的所有模式下都打开

引脚复用为GPIO:
LED:  SW_MUX_CTL_PAD_GPIO1_IO03   0x020E_0068 b[3:0]=0b_0101, GPIO1_IO03
BEEP: SW_MUX_CTL_PAD_SNVS_TAMPER1 0x0229_000C b[3:0]=0b_0101, GPIO5_IO01

引脚电气属性:
LED:  SW_PAD_CTL_PAD_GPIO1_IO03   0x020E_02F4 default=0x000010B0
BEEP: SW_PAD_CTL_PAD_SNVS_TAMPER1 0x0229_0050 default=0x000110A0

GPIO方向:
LED:  GPIO1_GDIR 0x0209_C004 b[3]=0b_1, OUTPUT
BEEP: GPIO5_GDIR 0x020A_C004 b[1]=0b_1, OUTPUT

GPIO电平:
LED:  GPIO1_DR 0x0209_C000 b[3]
BEEP: GPIO5_DR 0x020A_C000 b[1]
*/

#define GPIO_NUM (2)

typedef struct
{
    u32 reg_addr_clk;       // GPIO的时钟使能寄存器
    int reg_bit_clk;        // GPIO引脚的时钟使能位偏移

    u32 reg_addr_iomux;     // GPIO引脚复用寄存器

    u32 reg_addr_pad;       // GPIO引脚电气属性寄存器   
    u32 pad_default;        // GPIO引脚默认电气属性

    u32 reg_addr_gdir;      // GPIO引脚方向寄存器
    int reg_bit_gdir;       // GPIO引脚的方向位偏移

    u32 reg_addr_dr;        // GPIO引脚电平值寄存器
    int reg_bit_dr;         // GPIO引脚电平值位偏移

    int active_level;       // GPIO引脚的有效电平
} gpios_t;

typedef struct
{
    void __iomem *vir_addr_clk;
    void __iomem *vir_addr_iomux;
    void __iomem *vir_addr_pad;
    void __iomem *vir_addr_gdir;
    void __iomem *vir_addr_dr;
} gpio_vir_addr_t;

typedef struct
{
    dev_t gpio_dev;
    struct cdev gpio_cdev;
    struct class *gpio_class;
    struct device *gpio_device[GPIO_NUM];
} chrdev_info_t;

const gpios_t gpios[GPIO_NUM] = 
{
    {
        .reg_addr_clk = 0x020C406C,
        .reg_bit_clk = 26,
        .reg_addr_iomux = 0x020E0068,
        .reg_addr_pad =  0x020E02F4,
        .pad_default = 0x000010B0,
        .reg_addr_gdir = 0x0209C004,
        .reg_bit_gdir = 3,
        .reg_addr_dr = 0x0209C000,
        .reg_bit_dr = 3,
        .active_level = 0,
    },
    {
        .reg_addr_clk = 0x020C406C,
        .reg_bit_clk = 30,
        .reg_addr_iomux = 0x0229000C,
        .reg_addr_pad =  0x02290050,
        .pad_default = 0x000110A0,
        .reg_addr_gdir = 0x020AC004,
        .reg_bit_gdir = 1,
        .reg_addr_dr = 0x020AC000,
        .reg_bit_dr = 1,
        .active_level = 0,
    }
};

gpio_vir_addr_t vir_addrs[GPIO_NUM] = { { NULL}, { NULL } };
chrdev_info_t gpio_chrdev;

static int init_gpios(void)
{
    int i;
    u32 val;

    for (i = 0; i < GPIO_NUM; i++)
    {
        vir_addrs[i].vir_addr_clk = ioremap(gpios[i].reg_addr_clk, 4);
        vir_addrs[i].vir_addr_iomux = ioremap(gpios[i].reg_addr_iomux, 4);
        vir_addrs[i].vir_addr_pad = ioremap(gpios[i].reg_addr_pad, 4);
        vir_addrs[i].vir_addr_gdir = ioremap(gpios[i].reg_addr_gdir, 4);
        vir_addrs[i].vir_addr_dr = ioremap(gpios[i].reg_addr_dr, 4);

        if (!vir_addrs[i].vir_addr_clk || !vir_addrs[i].vir_addr_iomux ||
            !vir_addrs[i].vir_addr_pad || !vir_addrs[i].vir_addr_gdir ||
            !vir_addrs[i].vir_addr_dr)
            goto error;
    }

    for (i = 0; i < GPIO_NUM; i++)
    {
        // 使能GPIO口时钟
        val = readl(vir_addrs[i].vir_addr_clk);
        val &= ~(0x3 << gpios[i].reg_bit_clk);
        writel(val | (0x3 << gpios[i].reg_bit_clk), vir_addrs[i].vir_addr_clk);

        // 设置引脚复用为GPIO
        writel(0x5, vir_addrs[i].vir_addr_iomux);

        // 恢复引脚的默认电气属性
        writel(gpios[i].pad_default, vir_addrs[i].vir_addr_pad);

        // 设置GPIO为输出
        val = readl(vir_addrs[i].vir_addr_gdir);
        val &= ~(0x1 << gpios[i].reg_bit_gdir);
        writel(val | (0x1 << gpios[i].reg_bit_gdir), vir_addrs[i].vir_addr_gdir);

        // 输出无效电平
        val = readl(vir_addrs[i].vir_addr_dr);
        val &= ~(0x1 << gpios[i].reg_bit_dr);
        writel(val | ((gpios[i].active_level ? 0x0 : 0x1) << gpios[i].reg_bit_dr), vir_addrs[i].vir_addr_dr);
    }

    return 0;

error:
    for (i = 0; i < GPIO_NUM; i++)
    {
        if (vir_addrs[i].vir_addr_clk)
        {
            iounmap(vir_addrs[i].vir_addr_clk);
            vir_addrs[i].vir_addr_clk = NULL;
        }
        if (vir_addrs[i].vir_addr_iomux)
        {
            iounmap(vir_addrs[i].vir_addr_iomux);
            vir_addrs[i].vir_addr_iomux = NULL;
        }
        if (vir_addrs[i].vir_addr_pad)
        {
            iounmap(vir_addrs[i].vir_addr_pad);
            vir_addrs[i].vir_addr_pad = NULL;
        }
        if (vir_addrs[i].vir_addr_gdir)
        {
            iounmap(vir_addrs[i].vir_addr_gdir);
            vir_addrs[i].vir_addr_gdir = NULL;
        }
        if (vir_addrs[i].vir_addr_dr)
        {
            iounmap(vir_addrs[i].vir_addr_dr);
            vir_addrs[i].vir_addr_dr = NULL;
        }
    }

    return -1;
}

static void deinit_gpios(void)
{
    int i;
    u32 val;

    for (i = 0; i < GPIO_NUM; i++)
    {
        // 输出无效电平
        val = readl(vir_addrs[i].vir_addr_dr);
        val &= ~(0x1 << gpios[i].reg_bit_dr);
        writel(val | ((gpios[i].active_level ? 0x0 : 0x1) << gpios[i].reg_bit_dr), vir_addrs[i].vir_addr_dr);
    }

    for (i = 0; i < GPIO_NUM; i++)
    {
        // 释放页表
        iounmap(vir_addrs[i].vir_addr_clk);
        iounmap(vir_addrs[i].vir_addr_iomux);
        iounmap(vir_addrs[i].vir_addr_pad);
        iounmap(vir_addrs[i].vir_addr_gdir);
        iounmap(vir_addrs[i].vir_addr_dr);

        vir_addrs[i].vir_addr_clk = NULL;
        vir_addrs[i].vir_addr_iomux = NULL;
        vir_addrs[i].vir_addr_pad = NULL;
        vir_addrs[i].vir_addr_gdir = NULL;
        vir_addrs[i].vir_addr_dr = NULL;
    }
}

static void set_gpio_value(int index, int value)
{
    u32 val;

    val = readl(vir_addrs[index].vir_addr_dr);
    val &= ~(0x1 << gpios[index].reg_bit_dr);
    writel(val | ((value ? 0x1 : 0x0) << gpios[index].reg_bit_dr), vir_addrs[index].vir_addr_dr);
}

static int gpio_open(struct inode *inode, struct file *file)
{
    int minor = MINOR(inode->i_rdev);

    if (minor >= GPIO_NUM)
    {
        printk(KERN_ERR "invalid file node\n");
        return -1;
    }

    return 0;
}

static int gpio_release(struct inode * inode, struct file * file)
{
    int minor = MINOR(inode->i_rdev);

    if (minor >= GPIO_NUM)
    {
        printk(KERN_ERR "invalid file node\n");
        return -1;
    }

    return 0;
}

static ssize_t gpio_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{
    char databuf[32] = { '\0' };
    size_t datalen = 0;
    int minor = MINOR(file->f_inode->i_rdev);

    if (minor >= GPIO_NUM)
    {
        printk(KERN_ERR "invalid file node\n");
        return -1;
    }

    datalen = count >= sizeof(databuf) - 1 ? sizeof(databuf) - 1 : count;

    if (copy_from_user(databuf, buffer, datalen))
    {
        printk(KERN_ERR	"copy from user failed\n");
        return -1;
    }

    if (sysfs_streq(databuf, "ON"))
    {
        set_gpio_value(minor, gpios[minor].active_level ? 1 : 0);
    }
    else if (sysfs_streq(databuf, "OFF"))
    {
        set_gpio_value(minor, gpios[minor].active_level ? 0 : 1);
    }
    else
    {
        printk(KERN_WARNING "unsupported command: %s\n", databuf);
    }

    return count;
}

static const struct file_operations gpio_fops =
{ 
    .open = gpio_open,
    .release = gpio_release,
    .write = gpio_write,
};

static int __init gpio_basic_init(void)
{
    int i, ret;

    // 申请设备号
    ret = alloc_chrdev_region(&gpio_chrdev.gpio_dev, 0, GPIO_NUM, "gpios");
    if (ret < 0)
    {
        printk(KERN_ERR "can't allocate dev number\n");
        return -1;
    }

    // 设置字符设备驱动对象,添加操作函数集
    gpio_chrdev.gpio_cdev.owner = THIS_MODULE;
    cdev_init(&gpio_chrdev.gpio_cdev, &gpio_fops);

    // 注册字符设备驱动,绑定设备号与字符设备驱动对象
    cdev_add(&gpio_chrdev.gpio_cdev, gpio_chrdev.gpio_dev, GPIO_NUM);

    // 初始化GPIO
    if (init_gpios() != 0)
        goto error;

    // 创建类和设备
    gpio_chrdev.gpio_class = class_create(THIS_MODULE, "gpios");
    if (IS_ERR(gpio_chrdev.gpio_class))
        goto error;

    for (i = 0; i < GPIO_NUM; i++)
    {
        gpio_chrdev.gpio_device[i] = device_create(gpio_chrdev.gpio_class, NULL,
                                          MKDEV(MAJOR(gpio_chrdev.gpio_dev), i), NULL, "%s-%d", "gpio", i);
        if (IS_ERR(gpio_chrdev.gpio_device[i]))
            goto error;
    }

    return 0;

error:
    for (i = 0; i < GPIO_NUM; i++)
    {
        if (gpio_chrdev.gpio_device[i])
        {
            device_destroy(gpio_chrdev.gpio_class,  MKDEV(MAJOR(gpio_chrdev.gpio_dev), i));
            gpio_chrdev.gpio_device[i] = NULL;
        }
    }
    
    if (gpio_chrdev.gpio_class)
    {
        class_destroy(gpio_chrdev.gpio_class);
        gpio_chrdev.gpio_class = NULL;
    }

    cdev_del(&gpio_chrdev.gpio_cdev);
    unregister_chrdev_region(gpio_chrdev.gpio_dev, GPIO_NUM);

    return -1;
}

static void __exit gpio_basic_exit(void)
{
    int i;

    for (i = 0; i < GPIO_NUM; i++)
    {
        if (gpio_chrdev.gpio_device[i])
        {
            device_destroy(gpio_chrdev.gpio_class, MKDEV(MAJOR(gpio_chrdev.gpio_dev), i));
            gpio_chrdev.gpio_device[i] = NULL;
        }
    }
    
    if (gpio_chrdev.gpio_class)
    {
        class_destroy(gpio_chrdev.gpio_class);
        gpio_chrdev.gpio_class = NULL;
    }

    deinit_gpios();
    cdev_del(&gpio_chrdev.gpio_cdev);
    unregister_chrdev_region(gpio_chrdev.gpio_dev, GPIO_NUM);
}

module_init(gpio_basic_init);
module_exit(gpio_basic_exit);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("David");
MODULE_DESCRIPTION("basic gpio driver");
MODULE_ALIAS("Basic GPIO");

Makefile文件

bash 复制代码
KERNEL_DIR := /home/david/share/driver-uboot-linux/linux

ARCH := arm
CROSS_COMPILE := arm-linux-gnueabihf-

export ARCH CROSS_COMPILE

module_name := class_dev
install_dir := /home/david/share/nfs/rootfs/modules/$(module_name)

obj-m := $(module_name).o

.PHONY: all clean install

all:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules

clean:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean

install: all
	@if [ ! -d "$(install_dir)" ]; then \
        echo "Creating $(install_dir) directory..."; \
        mkdir -p $(install_dir); \
	fi

	cp *.ko $(install_dir)
相关推荐
HLC++17 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室18 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy21 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕21 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫1 天前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生1 天前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.111 天前
linux基础IO
linux·运维·服务器
BelongPanda1 天前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯1 天前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_777998341 天前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器