十八、使用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)
相关推荐
张太行_3 小时前
Linux静态库:多模块高效管理
linux·运维·服务器
公子烨4 小时前
linux的斩杀线之OOM操控
linux
wgl6665204 小时前
Linux---基础IO!
linux·运维·服务器
Ancelin安心4 小时前
kali-dirsearch的使用
linux·运维·服务器·python·计算机网络·web安全·网络安全
IT利刃出鞘5 小时前
VMware--解决vmdk越来越大的问题(vmdk瘦身)
linux·ubuntu·vmware
wdfk_prog6 小时前
[Linux]学习笔记系列 -- [driver]base
linux·笔记·学习
月光下的麦克6 小时前
如何查案动态库版本
linux·运维·c++
Vallelonga6 小时前
使用 busybox 制作磁盘镜像文件
linux·经验分享
EndingCoder6 小时前
索引类型和 keyof 操作符
linux·运维·前端·javascript·ubuntu·typescript
石小千6 小时前
Linux清除缓存
linux·运维