Linux字符设备驱动-LED

实现了3个led灯的驱动程序,加载模块后,在/sys/class目录下生成my_led_class类,类里面有redgreenblue三个属性文件,在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_addcdev_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目录下创建出属性文件,同时在驱动程序中实现了showstore函数后,便可以使用catecho命令对创建出来的设备属性文件进行读写,从而达到控制设备的目的。
使用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;
	}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值