十八、使用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)
相关推荐
@noNo2 小时前
VMware Workstation 虚拟机 Ubuntu 24.04 主机与虚拟机之间无法复制粘贴
linux·运维·ubuntu
驱动开发0072 小时前
UVC 红外相机初始化流程 setup包解析
驱动开发·数码相机·云计算·usb重定向
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [fs]initramfs
linux·笔记·学习
Violet_YSWY2 小时前
CentOS 的 DVD 镜像 和 Minimal 镜像 的区别
linux·运维·centos
霜!!2 小时前
openssl升级
linux·运维·服务器
Truman楚门2 小时前
Page cache
linux·内存管理
mzhan0172 小时前
[晕事]今天做了件晕事98,把openssl-libs 强制删掉了
linux·网络·晕事·openssl-libs
Saniffer_SH3 小时前
【每日一题】笔记本电脑上从U盘拷贝文件到M.2 SSD过程中为什么链路还会偶尔进入L1.2低功耗?
服务器·网络·人工智能·驱动开发·单片机·嵌入式硬件·电脑
DIY机器人工房3 小时前
简单理解:珠海航宇微科技(航宇微)、芯探索、XM1002他们之间的关系
科技·单片机·嵌入式·diy机器人工房·芯探索·xm1002·航宇微