实现了3个led灯的驱动程序,加载模块后,在/sys/class
目录下生成my_led_class
类,类里面有red
、green
、blue
三个属性文件,在dev
目录下生成my_led
设备节点。
测试程序的测试命令如下:
./my_led_test -r <on/off> -g <on/off> -b <on/off>
-r 红灯
-g 绿灯
-b 蓝等
on 亮
off 灭
也可通过读写属性文件进行控制:
echo <num> > <red/green/blue> // num为0,灯灭,num非0,灯亮
cat <red/green/blue> // 查看3个led等亮的次数
内核字符设备知识
1.模块初始化和退出
1.1.初始化模块
include <linux/module.h>
static int __init initialization_function(void) {
// Initialization code here
}
module_init(initialization_function)
初始化函数应该申明为static
初始化函数返回类型为int
,初始化成功返回0,错误返回负值
__init
修饰的函数表示该函数只在模块加载时使用,加载完毕便会丢弃这个函数,以释放内存
module_init
是强制使用的,这个宏定义增加了特别的段到模块目标代码中,表明在哪里找到模块的初始化函数
1.2.退出模块
include <linux/module.h>
static void __exit cleanup_function(void) {
// cleanup code here
}
module_exit(cleanup_function)
每个非试验性的模块要求有一个退出函数,以注销接口,将资源返回给操作系统
退出函数的返回类型为void,没有返回值
__exit
表示次函数只用于模块卸载,编译器将__exit
修饰的函数放在特殊的ELF段
如果模块没有定义退出函数,则内核不允许该模块被卸载
2.设备号
在内核中,dev_t
用来持有设备编号,设备编号包括主设备号和次设备号。在32位平台上,dev_t
类型的变量长度为32位,高12位用作主设备号,低20位用作次设备号。
include <linux/types.h>
MAJOR(dev_t dev) // 通过设备号获取主设备号
MINOR(dev_t dev) // 通过设备号获取次设备号
MKDEV(int major, int minor) // 通过主设备号和次设备号获取设备号
2.1.分配设备号
2.1.1.指定设备号
register_chrdev_region
函数用于向系统注册需要使用的设备号,此时的设备号是指定的,由from
参数进行指定,count
表示需要设备号的数量,name
为注册设备的名称。成功返回0,失败返回非0。
register_chrdev_region
内部调用__register_chrdev_region
函数进行设备号的注册,但在注册之前会检查注册的设备号是否超出范围。而__register_chrdev_region
不做类似的检查。
include <linux/fs.h>
int register_chrdev_region(dev_t from, unsigned count, const char *name)
2.1.2.动态分配设备号
alloc_chrdev_region
函数用于动态分配设备号,此时*dev
为0,不用人为指定设备号,当系统分配成功时,dev
存储系统分配好的设备号。成功返回0,失败返回非0。此函数的好处是避免了人工指定设备号导致设备号冲突的事件发生,由内核统一分配设备号,推荐使用此函数。
include <linux/fs.h>
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
2.2.释放设备号
unregister_chrdev_region
函数用于释放分配的设备号。from
为释放的起始设备号,count
为释放设备号的数量。
include <linux/fs.h>
void unregister_chrdev_region(dev_t from, unsigned count)
3.文件相关结构体
在Linux操作系统上,一些皆文件,设备也被看作是文件,由文件系统统一管理。Linux文件系统有三个重要的数据结构体,分别为struct inode struct file struct file_operations
,理解这三个数据结构,对编写驱动程序有很大的帮助。这三个结构体都在<linux/fs.h>
头文件中。
3.1.struct inode
文件系统处理文件所需要的所有信息都放在inode
结构体中,该结构体被称为索引节点。文件名可以被随时修改,但索引节点是唯一的(不随文件名变更),即每个文件(和目录)可能会有多个表示打开文件的struct file
结构体,但有且只有一个对应的inode
。
struct inode {
dev_t i_rdev; // 表示设备文件时需要的设备号
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev; // inode表示字符设备时,指向字符设备结构体
};
};
3.2.struct file
file
被称文件对象,描述进程怎样与一个打开的文件进行交互。file
结构体代表一个打开的文件。它由内核在open
文件时创建,并传递给在该文件上进行操作的所有函数,在close
后内核会释放该结构体。存放在file中的主要信息是文件当前读写位置、文件标志、文件操作函数、目录等信息。file
结构体不仅仅限定于设备驱动程序,系统中每个打开的文件在内核空间都有一个对应的file
结构体。
struct file {
const struct file_operations *f_op; // 文件操作函数指针集合
unsigned int f_flags; // 文件标志,如阻塞或者非阻塞
fmode_t f_mode; // 文件模式
loff_t f_pos; // 文件的当前读写位置
void *private_data; // 私有数据
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
3.2.struct file_operations
file_operations
是一个函数指针集合,每个打开文件的操作方法都由file_operations
描述,包含了该文件的所有操作方法,struct file
中的f_op
指针指向了一个file_operations
结构体,这些操作大部分负责实现系统调用。由于设备在Linux上也被看成文件,因此也要提供file_operations
结构体,因此在编写设备驱动程序的时候,需要提供该设备的操作方法,将操作函数指针填充到file_operations
结构体中,然后注册到系统中。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
};
4.字符设备的注册
Linux内核内部使用struct cdev
结构来表示字符设备。字符设备在加载的时候必须分配并注册一个或多个cdev
结构体,cdev
结构体的数量和注册的设备数量一致。cdev
结构体包含指向设备操作函数结构体file_operations
的指针、设备号dev
等。cdev
中的owner
变量应该被设置为THIS_MODULE
,这里需要在驱动程序里手动设置。THIS_MODULE
定义为#define THIS_MODULE (&__this_module)
,__this_module
是一个struct module
变量,代表当前模块,跟current
有几分相似。可以通过THIS_MODULE
宏来引用当前模块的struct module
结构体。
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;
};
void cdev_init(struct cdev *, const struct file_operations *); // 初始化
struct cdev *cdev_alloc(void); // 分配cdev结构体
int cdev_add(struct cdev *, dev_t, unsigned); // 注册字符设备
void cdev_del(struct cdev *); // 移除字符设备
在使用cdev_add
时,需要牢记重要的一点,首先,这个调用可能会失败,如果返回一个负的错误码,表示调用失败,则设备不会添加到系统中,如果返回0,则表示该设备注册成功,则从这之后该设备随时可能被系统使用,因此在驱动程序还没有完全准备好处理设备上的操作时,就不能调用cdev_add
。cdev_del
函数用于注销系统中的字符设备。需要注意的是,调用cdev_del
函数之后,就不能再使用设备了,也不能访问cdev
结构体了。
4.自动创建设备节点
使用下面两个函数后,会在/sys/class目录下生成类文件,此udev或者mdev会响应device_create函数,在/dev目录下自动创建设备节点,不需要手动使用mknode
命令创建设备节点。
include <linux/device.h>
class_create(owner, name) // 宏定义,创建类,返回值类型struct class *
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...) // 创建设备
void class_destroy(struct class *cls); // 移除类
void device_destroy(struct class *class, dev_t devt); // 移除设备
5.设备属性
在Linux驱动程序编写中,使用sysfs的API函数,可以在sys目录下创建出属性文件,同时在驱动程序中实现了show
和store
函数后,便可以使用cat
和echo
命令对创建出来的设备属性文件进行读写,从而达到控制设备的目的。
使用DEVICE_ATTR
宏,可以定义一个设备属性。使用ATTRIBUTE_GROUPS
定义一组设备属性,使用__ATTRIBUTE_GROUPS
定义多组设备属性。
#define DEVICE_ATTR(_name, _mode, _show, _store)
#define ATTRIBUTE_GROUPS(_name)
#define __ATTRIBUTE_GROUPS(_name)
定义好属性后,需要调用下面的函数将定义的属性文件添加到系统中去,添加成功后,会在/sys/class目录下生成属性文件。device_create_file
内部调用了sysfs_create_file
函数,该函数将属性添加到kobject
中,在sys目录中,一个kobject
代表一个目录。
#include <linux/sysfs.h>
// 创建一个属性文件
int device_create_file(struct device *dev, const struct device_attribute *attr);
// 移除一个属性文件
void device_remove_file(struct device *dev, const struct device_attribute *attr);
// 创建一个属性文件
int __must_check sysfs_create_files(struct kobject *kobj, const struct attribute **attr);
// 移除一个属性文件
void sysfs_remove_files(struct kobject *kobj, const struct attribute **attr);
// 创建一组属性文件
int sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)
// 移除一组属性文件
void sysfs_remove_group(struct kobject *kobj, const struct attribute_group *grp)
// 创建多组属性文件
int sysfs_create_groups(struct kobject *kobj, const struct attribute_group **groups)
// 移除多组属性文件
void sysfs_remove_groups(struct kobject *kobj, const struct attribute_group **groups)
6.I/O内存
6.1.映射I/O内存
要访问IO空间,需要使用ioremap将物理内存映射为虚拟内存,iounmap解除映射。这两个函数跟体系结构密切相关。
#include <asm/io.h>
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
#define iounmap __arm_iounmap
#### 6.2.访问I/O内存
#include <asm/io.h>
unsigned int ioread32(void __iomem *addr) // 读取4字节
void iowrite32(u32 val, void __iomem *addr) // 写入4字节
7.用户空间和内核空间数据交换
用户空间和内核空间交换数据,必须经过严格的检查才可以进行,必须使用专用的函数。copy_from_user
函数将数据从用户空间拷贝到内核空间,copy_to_user
将数据从内核空间拷贝到用户空间。拷贝成功返回0,失败返回非0.
#include <asm/uaccess.h>
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
8.同步
通常情况下,led只允许一个进程或者线程操作,而Linux是多任务系统,存在多个进程或者线程操作led的可能,因此必须添加同步措施,保证当前进程或者线程操作完毕之后,下一个进程或者线程才能操作led。
include <linux/mutex.h>
#define mutex_init(mutex); // 初始化互斥体
void mutex_lock(struct mutex *lock); // 互斥体加锁
void mutex_unlock(struct mutex *lock); // 互斥体解锁
9.字符设备驱动源码
/*===================================my_led.h========================================*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/atomic.h>
#include <asm/io.h>
// GPIO1_4 红灯
// GPIO4_20 绿灯
// GPIO4_19 蓝灯
// GPIO1寄存器地址定义
#define GPIO1_DR (0x0209C000) // 数据寄存器
#define GPIO1_DRIR (0x0209C004) // 方向寄存器,0为输入,1为输出
#define GPIO1_PSR (0x0209C008) // 状态寄存器
#define GPIO1_ICR1 (0x0209C00C) // 中断配制寄存器1
#define GPIO1_ICR2 (0x0209C010) // 中断配制寄存器2
#define GPIO1_ICRIMR (0x0209C014) // 中断掩码寄存器
#define GPIO1_ICRISR (0x0209C018) // 中断状态寄存器
#define GPIO1_EDGE_SEL (0x0209C01C) // 边缘选择寄存器,用以配制双边沿触发中断
// GPIO1_4掩码(PWM3.OUT)
#define GPIO1_4_MASK (1 << 4)
// GPIO1_4引脚复用控制寄存器
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 (0x20E0000 + 0x6C)
// GPIO1_4引脚复用模式为ATL5
#define ATL5 (5)
#define GPIO1_4_MUX_MODE ATL5
// GPIO1_4引脚控制寄存器,配制电气属性,如上下拉等
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04 (0x20E0000 + 0x2F8)
// GPIO1时钟控制寄存器
#define CCM_CCGR1 (0x20C4000 + 0x6C)
#define GPIO1_4_CLK_MASK (3 << 26)
// GPIO4寄存器地址定义
#define GPIO4_DR (0x020A8000) // 数据寄存器
#define GPIO4_DRIR (0x020A8004) // 方向寄存器,0为输入,1为输出
#define GPIO4_PSR (0x020A8008) // 状态寄存器
#define GPIO4_ICR1 (0x020A800C) // 中断配制寄存器1
#define GPIO4_ICR2 (0x020A8010) // 中断配制寄存器2
#define GPIO4_ICRIMR (0x020A8014) // 中断掩码寄存器
#define GPIO4_ICRISR (0x020A8018) // 中断状态寄存器
#define GPIO4_EDGE_SEL (0x020A801C) // 边缘选择寄存器,用以配制双边沿触发中断
// GPIO4_19掩码(PWM7.OUT)
#define GPIO4_19_MASK (1 << 19)
// GPIO4_20掩码(PWM8.OUT)
#define GPIO4_20_MASK (1 << 20)
// GPIO4_19(PWM7.OUT)引脚复用控制寄存器
#define IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC (0x20E0000 + 0x1DC)
// GPIO4_19(PWM7.OUT)引脚控制寄存器,配制电气属性,如上下拉等
#define IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC (0x20E0000 + 0x468)
// GPIO4_20(PWM8.OUT)引脚复用控制寄存器
#define IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC (0x20E0000 + 0x1E0)
// GPIO4_20(PWM8.OUT)引脚控制寄存器,配制电气属性,如上下拉等
#define IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC (0x20E0000 + 0x46C)
// GPIO4_19引脚和GPIO4_20复用模式为ATL5
#define ATL5 (5)
#define GPIO4_19_MUX_MODE ATL5
#define GPIO4_20_MUX_MODE ATL5
// GPIO4时钟控制寄存器
#define CCM_CCGR3 (0x20C4000 + 0x74)
#define GPIO4_19_CLK_MASK (3 << 12)
#define GPIO4_20_CLK_MASK (3 << 12)
// 添加__iomem标记,编译器会检查此地址,确保在io空间
struct led_ctl_pin
{
unsigned int __iomem *dr_pin;
unsigned int __iomem *drir_pin;
unsigned int __iomem *mux_pin;
unsigned int __iomem *ctl_pin;
unsigned int __iomem *clk_pin;
};
// 设备结构体
struct my_led_dev {
struct cdev cdev; // 字符设备结构体
struct led_ctl_pin r_pin; // 红色led引脚
struct led_ctl_pin g_pin; // 绿色led引脚
struct led_ctl_pin b_pin; // 蓝色led引脚
dev_t devno; // 设备号
struct class* my_led_class;
struct device* my_led_device;
struct mutex mutex; // 用于同步的互斥体
unsigned int r_on_cnt; // 红色led亮的次数
unsigned int g_on_cnt; // 红色led亮的次数
unsigned int b_on_cnt; // 红色led亮的次数
};
/*===================================my_led.c========================================*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/sysfs.h>
#include "my_led.h"
static struct my_led_dev* my_led = NULL;
// ioread32和iowrite32为内核提供的函数,用于读写IO内存,内部插入了内存屏障
// ioread32的内存屏障插在读之后,确保读完之后才能执行后面的代码
// iowrite32的内存屏障插在写之前,确保写之前的代码执行完毕
// ioread32和iowrite32内部将指针由u32*强制转换为volatile u32*,保证不被编译器优化
// ioread32和iowrite32内部调用的是readl和writel宏,该宏不检查类型,不建议直接使用
static inline void r_led_open(struct my_led_dev* dev)
{
unsigned int val = 0;
val = ioread32(dev->r_pin.clk_pin);
val |= GPIO1_4_CLK_MASK; // 开启GPIO1时钟
iowrite32(val, dev->r_pin.clk_pin);
val = ioread32(dev->r_pin.mux_pin);
val &= ~0xF; // 将复用模式选择的低4位清零
val |= GPIO1_4_MUX_MODE; // 配制引脚复用为GPIO1_4
iowrite32(val, dev->r_pin.mux_pin);
val = ioread32(dev->r_pin.drir_pin);
val |= GPIO1_4_MASK; // 配置引脚为输出,1为输出,0为输入
iowrite32(val, dev->r_pin.drir_pin);
}
static inline void g_led_open(struct my_led_dev* dev)
{
unsigned int val = 0;
val = ioread32(dev->g_pin.clk_pin);
val |= GPIO4_20_CLK_MASK; // 开启GPIO4的时钟
iowrite32(val, dev->g_pin.clk_pin);
val = ioread32(dev->g_pin.mux_pin);
val &= ~0xF; // 将复用模式选择的低4位清零
val |= GPIO4_20_MUX_MODE; // 配制引脚复用为GPIO4_20
iowrite32(val, dev->g_pin.mux_pin);
val = ioread32(dev->g_pin.drir_pin);
val |= GPIO4_20_MASK; // 配置引脚为输出,1为输出,0为输入
iowrite32(val, dev->g_pin.drir_pin);
}
static inline void b_led_open(struct my_led_dev* dev)
{
unsigned int val = 0;
val = ioread32(dev->b_pin.clk_pin);
val |= GPIO4_19_CLK_MASK; // 开启GPIO4的时钟
iowrite32(val, dev->b_pin.clk_pin);
val = ioread32(dev->b_pin.mux_pin);
val &= ~0xF; // 将复用模式选择的低4位清零
val |= GPIO4_19_MUX_MODE; // 配制引脚复用为GPIO4_19
iowrite32(val, dev->b_pin.mux_pin);
val = ioread32(dev->b_pin.drir_pin);
val |= GPIO4_19_MASK; // 配置引脚为输出,1为输出,0为输入
iowrite32(val, dev->b_pin.drir_pin);
}
static int my_led_open(struct inode* inode, struct file* filp)
{
struct my_led_dev* dev = container_of(inode->i_cdev, struct my_led_dev, cdev);
mutex_lock(&dev->mutex);
r_led_open(dev);
g_led_open(dev);
b_led_open(dev);
mutex_unlock(&dev->mutex);
filp->private_data = dev;
//printk(KERN_INFO "my_led module open OK\n");
return 0;
}
// 控制led灯点亮和熄灭,如果led灯由熄灭到点亮则记录点亮的次数
// 若led灯控制前状态和控制后的状态一致,则不做操作
static inline void led_ctl(struct my_led_dev* dev, void __iomem *pin, int val, int flag)
{
unsigned int reg = 0;
mutex_lock(&dev->mutex);
reg = ioread32(pin);
if (flag) { // 非0灯亮
if (val == (reg & val)) { // led灯处于熄灭状态,则需要点亮led灯
reg &= ~val; // 输出低电平,点亮led
iowrite32(reg, pin);
if ((dev->r_pin.dr_pin) == pin)
dev->r_on_cnt++;
if ((dev->g_pin.dr_pin) == pin)
dev->g_on_cnt++;
if ((dev->b_pin.dr_pin) == pin)
dev->b_on_cnt++;
goto unlock;
}
}
else { // 0灯灭
if (0 == (reg & val)) { // led灯处于点亮状态,则需要熄灭led灯
reg |= val; // 输出高电平,熄灭led
iowrite32(reg, pin);
}
}
unlock:
mutex_unlock(&dev->mutex);
}
static ssize_t my_led_write(struct file* filp, const char __user* buf, size_t size, loff_t* ppos)
{
struct my_led_dev* dev = filp->private_data;
int ret, val[3];
if (size != 4 * 3) return -EINVAL;
// 成功返回0,失败返回失败的数目
ret = copy_from_user(&val, buf, sizeof(val));
if (0 != ret) return -EFAULT;
// 非0-灯亮,0-灯灭,GPIO引脚输出低电平灯亮,输出高点平灯灭
led_ctl(dev, dev->r_pin.dr_pin, GPIO1_4_MASK, val[0]);
led_ctl(dev, dev->g_pin.dr_pin, GPIO4_20_MASK, val[1]);
led_ctl(dev, dev->b_pin.dr_pin, GPIO4_19_MASK, val[2]);
return sizeof(val);
}
// 文件操作函数结构体
static const struct file_operations my_led_fops = {
.owner = THIS_MODULE,
.write = my_led_write,
.open = my_led_open,
};
//映射控制三个led灯所需的寄存器
static int r_ioremap(struct my_led_dev* dev)
{
int ret = 0;
// 映射GPIO数据寄存器和方向控制寄存器
dev->r_pin.dr_pin = ioremap(GPIO1_DR, 4 * 2);
dev->r_pin.drir_pin = dev->r_pin.dr_pin + 1;
// 映射引脚复用控制寄存器和引脚控制寄存器
dev->r_pin.mux_pin = ioremap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04, 4);
dev->r_pin.ctl_pin = ioremap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04, 4);
// 映射时钟控制寄存器
dev->r_pin.clk_pin = ioremap(CCM_CCGR1, 4);
if ((NULL == dev->r_pin.dr_pin) || (NULL == dev->r_pin.mux_pin) ||
(NULL == dev->r_pin.ctl_pin) || (NULL == dev->r_pin.clk_pin)) {
ret = -1;
printk(KERN_ERR "r_ioremap() failed\n");
}
return ret;
}
static int g_ioremap(struct my_led_dev* dev)
{
int ret = 0;
dev->g_pin.dr_pin = ioremap(GPIO4_DR, 4 * 2);
dev->g_pin.drir_pin = dev->g_pin.dr_pin + 1;
dev->g_pin.mux_pin = ioremap(IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC, 4);
dev->g_pin.ctl_pin = ioremap(IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC, 4);
dev->g_pin.clk_pin = ioremap(CCM_CCGR3, 4);
if ((NULL == dev->g_pin.dr_pin) || (NULL == dev->g_pin.mux_pin) ||
(NULL == dev->g_pin.ctl_pin) || (NULL == dev->g_pin.clk_pin)) {
ret = -1;
printk(KERN_ERR "g_ioremap() failed\n");
}
return ret;
}
static int b_ioremap(struct my_led_dev* dev)
{
int ret = 0;
dev->b_pin.dr_pin = dev->g_pin.dr_pin;
dev->b_pin.drir_pin = dev->g_pin.drir_pin;
dev->b_pin.mux_pin = ioremap(IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC, 4);
dev->b_pin.ctl_pin = ioremap(IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC, 4);
dev->b_pin.clk_pin = dev->g_pin.clk_pin;
if ((NULL == dev->b_pin.dr_pin) || (NULL == dev->b_pin.mux_pin) ||
(NULL == dev->b_pin.ctl_pin) || (NULL == dev->b_pin.clk_pin)) {
ret = -1;
printk(KERN_ERR "b_ioremap() failed\n");
}
return ret;
}
// 注销已映射的三个led灯控制寄存器
static void r_iounmap(struct my_led_dev* dev)
{
iounmap(dev->r_pin.dr_pin);
iounmap(dev->r_pin.mux_pin);
iounmap(dev->r_pin.ctl_pin);
iounmap(dev->r_pin.clk_pin);
}
static void g_iounmap(struct my_led_dev* dev)
{
iounmap(dev->g_pin.dr_pin);
iounmap(dev->g_pin.mux_pin);
iounmap(dev->g_pin.ctl_pin);
iounmap(dev->g_pin.clk_pin);
}
static void b_iounmap(struct my_led_dev* dev)
{
iounmap(dev->b_pin.mux_pin);
iounmap(dev->b_pin.ctl_pin);
}
// 初始化和注册cdev结构体
static int set_up_my_led_cdev(struct my_led_dev* dev, int cnt)
{
int err;
cdev_init(&dev->cdev, &my_led_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, dev->devno, cnt); // 出错返回负值
if (err < 0)
printk(KERN_ERR "adding my_led cdev %d error, errno %d\n", cnt, err);
return err;
}
// 定义设备的属性
static ssize_t red_show(struct device* dev, struct device_attribute* attr,char* buf)
{
return sprintf(buf, "red led lighting times %u\n", my_led->r_on_cnt);
} // echo 非0,灯亮,echo 0,灯灭
static ssize_t red_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{ int err;
unsigned int val;
// 将echo输入得字符串转换为无符号整数,10表示十进制
err = kstrtouint(buf, 10, &val);
if (err)
return err;
led_ctl(my_led, my_led->r_pin.dr_pin, GPIO1_4_MASK, val);
return count;
}
static ssize_t green_show(struct device *dev, struct device_attribute *attr,char *buf)
{
return sprintf(buf, "green led lighting times %u\n", my_led->g_on_cnt);
}
static ssize_t green_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{ int err;
unsigned int val;
err = kstrtouint(buf, 10, &val);
if (err)
return err;
led_ctl(my_led, my_led->g_pin.dr_pin, GPIO4_20_MASK, val);
return count;
}
static ssize_t blue_show(struct device *dev, struct device_attribute *attr,char *buf)
{
return sprintf(buf, "blue led lighting times %u\n", my_led->b_on_cnt);
}
static ssize_t blue_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{ int err;
unsigned int val;
err = kstrtouint(buf, 10, &val);
if (err)
return err;
led_ctl(my_led, my_led->b_pin.dr_pin, GPIO4_19_MASK, val);
return count;
}
// 红色led设备属性,生成的属性结构体名称为dev_attr_red,类型为struct device_attribute,
// mode为0644,所属者可以读写,其他只能读
static DEVICE_ATTR(red, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, red_show, red_store);
// 绿色led设备属性,生成的属性结构体名称为dev_attr_green,类型为struct device_attribute,
// mode为0644,所属者可以读写,其他只能读
static DEVICE_ATTR(green, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, green_show, green_store);
// 蓝色led设备属性,生成的属性结构体名称为dev_attr_blue,类型为struct device_attribute,
// mode为0644,所属者可以读写,其他只能读
static DEVICE_ATTR(blue, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, blue_show, blue_store);
static struct attribute* led_colour_attrs[] = {
&dev_attr_red.attr,
&dev_attr_green.attr,
&dev_attr_blue.attr,
NULL,
};
/* 创建led颜色属性组,生成的属性结构体名称为led_colour_group,类型为static const
struct attribute_group,使用led_colour_attrs初始化
ATTRIBUTE_GROUPS(led_colour); */
// 使用ATTRIBUTE_GROUPS宏创建属性组时编译会出现警告,这里直接定义led颜色属性组
static struct attribute_group led_colour_group = {
.attrs = led_colour_attrs,
};
// 模块初始化
static int __init my_led_init(void)
{
int ret = 0;
dev_t devno = 0;
// 动态分配设备号,传入的devno参数为0,使用unregister_chrdev_region注销动态分配打设备号
ret = alloc_chrdev_region(&devno, 0, 1, "my_led");
if (ret < 0) {
printk(KERN_ERR "alloc_chrdev_region() failed %d\n", ret);
return ret;
}
// 分配设备结构体内存并将分配的内存清0
my_led = kzalloc(sizeof(struct my_led_dev), GFP_KERNEL);
if (NULL == my_led) {
ret = -ENOMEM;
printk(KERN_ERR "kzalloc() failed %d\n", ret);
goto unreg_chrdev;
}
my_led->devno = devno;
// 使用ioremap映射控制红 绿 蓝三个led所需的寄存器
if (0 != r_ioremap(my_led)) { ret = -ENOMEM; goto unreg_chrdev; }
if (0 != g_ioremap(my_led)) { ret = -ENOMEM; goto iounmap_r; }
if (0 != b_ioremap(my_led)) { ret = -ENOMEM; goto iounmap_g; }
//
ret = set_up_my_led_cdev(my_led, 1);
if (ret < 0) goto iounmap_b;
// 创建类和设备,当模块加载后会自动在/dev目录下生成设备节点
my_led->my_led_class = class_create(THIS_MODULE, "my_led_class");
if (IS_ERR(my_led->my_led_class)) {
ret = PTR_ERR(my_led->my_led_class);
printk(KERN_ERR "class_create() failed %d\n", ret);
goto del_cdev;
}
my_led->my_led_device = device_create(my_led->my_led_class, NULL, devno, NULL,
"my_led");
if (IS_ERR(my_led->my_led_device)) {
ret = PTR_ERR(my_led->my_led_device);
printk(KERN_ERR "device_create() failed %d\n", ret);
goto clean_class;
}
/* 使用device_create_file创建属性文件,一次只能创建一个,使用device_remove_file移除属性文件
ret = device_create_file(my_led->my_led_device, &dev_attr_red);
if (ret != 0) goto clean_device;
ret = device_create_file(my_led->my_led_device, &dev_attr_green);
if (ret != 0) goto clean_attr_red;
ret = device_create_file(my_led->my_led_device, &dev_attr_blue);
if (ret != 0) goto clean_attr_green;
*/
// 使用sysfs_create_group可以创建一组属性文件,sysfs_remove_group移除一组属性文件
// 使用sysfs_create_groups可以创建多组属性文件,ysfs_remove_groups移除多组属性文件
ret = sysfs_create_group(&my_led->my_led_device->kobj, &led_colour_group);
if(ret != 0) goto clean_device;
// 初始化互斥体,硬件一般不能由多个进程或者线程操作,需要同步措施
mutex_init(&my_led->mutex);
printk(KERN_INFO "my_led module init OK, major %u, minor %u\n", MAJOR(devno),
MINOR(devno));
return 0;
/*
clean_attr_green:
device_remove_file(my_led->my_led_device, &dev_attr_green);
clean_attr_red:
device_remove_file(my_led->my_led_device, &dev_attr_red);
*/
clean_device:
device_destroy(my_led->my_led_class, devno);
clean_class:
class_destroy(my_led->my_led_class);
del_cdev:
cdev_del(&my_led->cdev);
iounmap_b:
b_iounmap(my_led);
iounmap_g:
g_iounmap(my_led);
iounmap_r:
r_iounmap(my_led);
kfree(my_led);
my_led = NULL;
unreg_chrdev:
unregister_chrdev_region(devno, 1);
return ret;
}
// 模块注销
static void __exit my_led_exit(void)
{
sysfs_remove_group(&my_led->my_led_device->kobj, &led_colour_group);
//device_remove_file(my_led->my_led_device, &dev_attr_blue);
//device_remove_file(my_led->my_led_device, &dev_attr_green);
//device_remove_file(my_led->my_led_device, &dev_attr_red);
device_destroy(my_led->my_led_class, my_led->devno);
class_destroy(my_led->my_led_class);
cdev_del(&my_led->cdev);
b_iounmap(my_led);
g_iounmap(my_led);
r_iounmap(my_led);
unregister_chrdev_region(my_led->devno, 1);
kfree(my_led);
my_led = NULL;
printk(KERN_INFO "my_led module exit\n");
}
module_init(my_led_init);
module_exit(my_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liyang.plus@foxmail.com");
MODULE_VERSION("v1.00");
10.测试程序源码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define PATH "/dev/my_led"
#define OPT_STRING ":r:g:b:" // 选项字符串
//#define DEBUG
void print_usage()
{
printf("Usage: ./my_led_test -r <on/off> -g <on/off> -b <on/off>\n"
"-r: Control red led\n"
"-g: Control green led\n"
"-b: Control blue led\n"
"on: Light on(default)\n"
"off: Light off\n");
}
extern char* optarg; // 指向参数值
extern int optind; // 记录getopt函数处理argv[]数组的位置,一般不需要设置
extern int opterr; // 保存出错信息,非0 getopt会向stderr打印出错信息,如为0时则不打印
/* 遇到无法识别的选项,getopt返回?号,并将?存储到optopt中,如果将选项字符串第一个字符设置
为:号,那么getopt函数在用户未提供值的情况下返回:号而不是?号 */
extern int optopt;
int parse_option(int argc, char* argv[], int* rgb)
{
int opt;
while (-1 != (opt = getopt(argc, argv, OPT_STRING))) {
switch (opt) {
case 'r':
if(0 == strcmp(optarg, "on")) rgb[0] = 1;
else if (0 == strcmp(optarg, "off")) rgb[0] = 0;
else return -1;
#ifdef DEBUG
printf("option %c, optarg %s, r %d\n", opt, optarg, rgb[0]);
#endif
break;
case 'g':
if(0 == strcmp(optarg, "on")) rgb[1] = 1;
else if (0 == strcmp(optarg, "off")) rgb[1] = 0;
else return -1;
#ifdef DEBUG
printf("option %c, optarg %s, g %d\n", opt, optarg, rgb[1]);
#endif
break;
case 'b':
if(0 == strcmp(optarg, "on")) rgb[2] = 1;
else if (0 == strcmp(optarg, "off")) rgb[2] = 0;
else return -1;
#ifdef DEBUG
printf("option %c, optarg %s, b %d\n", opt, optarg, rgb[2]);
#endif
break;
case ':':
#ifdef DEBUG
printf("option needs a value\n");
#endif
print_usage();
return -1;
break;
case '?':
#ifdef DEBUG
printf("unknown option: %c\n", optopt);
#endif
print_usage();
return -1;
break;
default:
print_usage();
return -1;
break;
}
}
return 0;
}
int main(int argc, char* argv[])
{
int fd, ret, val[3] = {0};
if (7 != argc) {
print_usage();
return -1;
}
ret = parse_option(argc, argv, val);
if (0 != ret) {
print_usage();
printf("parse option error\n");
return -1;
}
fd = open(PATH, O_RDWR);
if (fd < 0){
printf("my_led open error\n");
return -1;
}
write(fd, &val, sizeof(val));
close(fd);
return 0;
}