设备的大管家class
- 使用
class对硬件设备进行分类管理 class与用户空间守护进程udev/mdev协作,自动创建设备文件
设备驱动模型框图

- Linux内核启动的过程中会调用
classes_init()函数在sysfs文件系统中创建一个名为class的文件夹。 - 我们在驱动中调用
class_create()函数,在class文件夹下创建一个指定名称的分类文件夹(比如指定分类名为xxx)。 - 我们继续在驱动中调用
device_create()函数,在指定的分类中又创建一个表示设备的文件夹(比如指定设备名为yyy)。调用这个函数的时候,我们会传入设备号,所以这个设备文件夹下会生成一个名为dev的设备属性文件存放设备号。 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)