嵌入式快速入门学习笔记-字符设备驱动开发(LED)

字符设备驱动

定义

以字节为单位进行操作,如:(LCD、串口、LED、蜂鸣器、触摸屏……)。

准备工作

编译任何驱动程序前,需要先编译内核,因为驱动程序需要用到内核中的一些文件。
原因:
驱动程序要用到内核中的一些文件。
编译驱动时用的内核要与开发板上运行的内核一致。
更换板子上的内核后,板子上其他驱动也要重新编译。

系统整体工作原理

应用层->API->设备驱动->硬件
API:open、read、write、closa等
驱动源码中提供真正的open、read、write、close等函数实体
(此处需要添加一张图片)

代码重要部分分析

file_operation结构体

所在目录:kernel/include/linux/fs.h

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 *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
};

作用:
驱动向内核注册时,用于挂接实体函数的地址。
每一个驱动程序代码中都需要该结构体类型的变量。

register_chrdev函数

所在目录:kernel/include/linux/fs.h

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
{
	return __register_chrdev(major, 0, 256, name, fops);
}

作用:
用于驱动向内核注册自己的file_operations。
将要注册的驱动存储在内核驱动数组的相应位置。
补:
查看内核中已经注册的驱动指令

cat /proc/devices

unregister_chrdev函数

所在目录:kernel/include/linux/fs.h

static inline void unregister_chrdev(unsigned int major, const char *name)
{
	__unregister_chrdev(major, 0, 256, name);
}

作用:
用于在内核中卸载安装的驱动。

代码编写

Makefile

# 开发板的linux内核的源码树目录
KERN_DIR = /x210v3_bsp/qt_x210v3/kernel

obj-m	+= module_test.o

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	arm-none-linux-gnueabi-gcc app.c -o app

cp:
	cp *.ko /home/ww/nfs_rootfs
	cp app /home/ww/nfs_rootfs
	

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf app

module_test.c

该示例为内核自动分配主设备号

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h> //包含file_operations,register_chrdv,unregister_chrdv声明的头文件
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/string.h>

#define MYNAME "chartest"
#define rGPJ0CON	*((volatile unsigned int *)S5PV210_GPJ0CON)
#define rGPJ0DAT	*((volatile unsigned int *)S5PV210_GPJ0DAT)

int MYMAJOR;
char kbuf[100];			// 内核空间的buf
//驱动硬件操作函数
//该函数中放置打开设备的硬件操作代码部分
static int test_chrdev_open(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_open success\n");
	return 0;
}

//驱动硬件关闭函数
static int test_chrdev_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "test_chrdev_release success\n");
	return 0;
}

// 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos)
{
	if (!copy_from_user(kbuf, ubuf, count))
	{
		printk(KERN_INFO "test_chrdev_write success\n");
		if(kbuf[0] == '1')
		{
			rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
		}
		else if(kbuf[0] == '0')
		{
			rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
		}
		else
		{
			printk(KERN_INFO "please input '1' or '0'\n");
		}
	}
	else
	{
		printk(KERN_INFO "test_chrdev_write fail\n");
	}
	return 0;
}

//驱动硬件读函数
ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
{
	if(!copy_to_user(ubuf, kbuf, count))
	{
		printk(KERN_INFO "test_chrdev_read success\n");
	}
	else
	{
		printk(KERN_INFO "test_chrdev_read fail\n");
	}
	return 0;
}

//自定义一个file_operations结构体变量,完成结构体内参数的填充
static const struct file_operations test_fops ={
	.owner		=	THIS_MODULE,		//默认即可
	.open		=	test_chrdev_open,	//对应驱动硬件操作函数
	.release	=	test_chrdev_release,	//对应驱动硬件关闭函数
	.write		=	test_chrdev_write,
	.read		=	test_chrdev_read,
};

//在module_init宏调用的函数中去注册字符设备驱动
static int __init chrdev_init(void)
{
	printk(KERN_INFO "chrdev_init \n");
	MYMAJOR= register_chrdev(0, MYNAME, &test_fops);//0-表示内核自动分配一个主设备号
	if(MYMAJOR < 0)
	{
		printk(KERN_ERR "register_chrdev fail\n");
		return -EINVAL;
	}
	else
	{
		printk(KERN_INFO "register_chrdev success\n");
		return 0;
	}
}

//在module_exit宏调用的函数中去注销字符设备驱动
static void __exit chrdev_exit(void)
{
	printk(KERN_INFO "chrdev_exit \n");
	unregister_chrdev(MYMAJOR, MYNAME);
}

module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("aston");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//需与 mknod /dev/test c 250 0 指令中的路径相同
#define FILE "dev/test"
char buf[100];

int main(void)
{
	int fd = -1;
	int i = 0;
	
	fd = open(FILE, O_RDWR);
	if (fd < 0)
	{
		printf("open %s error.\n", FILE);
		return -1;
	}
	printf("open %s success..\n", FILE);

	while (1)
	{
		memset(buf, 0 , sizeof(buf));
		printf("please input on | off | flash | quit | read\n");
		scanf("%s", buf);
		if (!strcmp(buf, "on"))
		{
			write(fd, "1", 1);
		}
		else if (!strcmp(buf, "off"))
		{
			write(fd, "0", 1);
		}
		else if (!strcmp(buf, "flash"))
		{
			for (i=0; i<3; i++)
			{
				write(fd, "1", 1);
				sleep(1);
				write(fd, "0", 1);
				sleep(1);
			}
		}
		else if (!strcmp(buf, "quit"))
		{
			break;
		}
		else if	(!strcmp(buf, "read"))
		{
			memset(buf, 0, sizeof(buf));
			read(fd, buf, 1);
			if(buf[0] == '1')
			{
				printf("led is on\n");
			}
			else if(buf[0] == '0')
			{
				printf("led is off\n");
			}
			else
			{
				printf("error\n");
			}
		}
	}
	// 关闭文件
	close(fd);
	
	return 0;
}

编译module_test,将.ko放入NFS共享目录

进入到Makefile和module_test.c这两个文件的文件夹目录

cd /mnt/hgfs/share/x210v3/5.2.10
make all
make cp

通过NFS传输 .ko 文件到开发板

假设Windows IP为192.168.1.100,在开发板上执行以下命令(注意:必须指定port为2049、 mountport为9999):

mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 192.168.1.100:/home/ww/nfs_rootfs /mnt

安装、查看、卸载

查看内核中已注册的驱动主设备号

cat /proc/devices

在这里插入图片描述
查看已经安装的驱动

lsmod

在这里插入图片描述
查看已有的设备文件

ls /dev/ -l

安装驱动

insmod module_test.ko

在这里插入图片描述
查看内核中已注册的驱动主设备号

cat /proc/devices

在这里插入图片描述
查看已经安装的驱动

lsmod

在这里插入图片描述
使用mknod创建设备文件

mknod /dev/test c 250 0

查看设备文件

ls /dev/test -l

在这里插入图片描述

测试app

./app

在这里插入图片描述
先卸载驱动

rmmod module_test

再删除设备文件

rm -r /dev/test

查看内核中已注册的驱动主设备号

cat /proc/devices

在这里插入图片描述
查看已经安装的驱动

lsmod

在这里插入图片描述

GPIOLIB方式注册

重新编译内核

执行 make menuconfig,去掉九鼎之前定义led驱动
在这里插入图片描述
选择上内核自己定义的led class
在这里插入图片描述
在这里插入图片描述
执行 make ,然后重新下载 zImage

代码编写

采用gpiolib的方式来控制led,实际使用的函数
(1)gpio_request:
向内核的gpiolib部分申请,得到允许后才可以去使用这个gpio。
(2)gpio_free:
对应gpio_request,用来释放申请后用完了的gpio
(3)gpiochip_is_requested:
接口用来判断某一个gpio是否已经被申请了
(4)gpio_direction_input / gpio_direction_output: 接口用来设置gpio为输入/输出模式
(5)gpio_set_value:
设置gpio输出值
(6)gpio_get_value:
获取gpio值

module_test.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h> 
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>	
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>

#define GPIO_LED1	S5PV210_GPJ0(3)

//驱动硬件写函数
static void s5pv210_led_set(struct led_classdev *led_cdev, enum led_brightness value)
{
	if(value == 1)
	{
		gpio_set_value(GPIO_LED1 , 0);
	}
	else if(value == 0)
	{
		gpio_set_value(GPIO_LED1 , 1);
	}
	else
	{
		printk(KERN_INFO "please input 0 or 1\n");
	}
}

//自定义一个led_classdev 结构体变量,完成结构体内参数的填充
static struct led_classdev led1 =
{
	.name			= "led1",
	.brightness		= 0,
	.max_brightness	= 255,
	.brightness_set	= s5pv210_led_set,
};

//在module_init宏调用的函数中去注册gpio_led设备驱动
static int __init s5pv210_led_init(void)
{
	int ret = -1;
	
	printk(KERN_INFO "s5pv210_led_init start\n");
	ret = gpio_request(GPIO_LED1, "led1_gpjo.3");
	if (ret < 0) 
	{
		printk(KERN_INFO "gpio_register failed\n");
		return -EINVAL;
	}
	else
	{
		printk(KERN_INFO "gpio_register success\n");
		gpio_direction_output(GPIO_LED1, 1);
	}
	
	ret = led_classdev_register(NULL, &led1);
	if (ret < 0) 
	{
		printk(KERN_INFO "led_classdev_register failed\n");
		return -EINVAL;
	}
	else
	{
		printk(KERN_INFO "led_classdev_register success\n");
	}
	
	return 0;
}

//在module_exit宏调用的函数中去注销gpio_led设备驱动
static void __exit s5pv210_led_exit(void)
{
	printk(KERN_INFO "s5pv210_led_exit start\n");
	gpio_free(GPIO_LED1);
	printk(KERN_INFO "s5pv210_led_exit end\n");
}

module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("ww");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息

makefile

# 开发板的linux内核的源码树目录
KERN_DIR = /x210v3_bsp/qt_x210v3/kernel

obj-m	+= module_test.o

all:
	make -C $(KERN_DIR) M=`pwd` modules 

cp:
	cp *.ko /home/ww/nfs_rootfs
	
.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean

编译、安装、测试

执行 make 指令,make cp指令,将编译得到的module_test.ko复制到nfs文件夹。
开发板执行nfs指令,传到开发板的 /mnt 目录

mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 192.168.1.100:/home/ww/nfs_rootfs /mnt

安装前先查看开发板原状态

mount -t debugfs debugfs /tmp
cat /tmp/gpio

使用完后umount /tmp卸载掉debugfs,防止引入错误。
在这里插入图片描述
在这里插入图片描述

安装驱动查看开发板状态

insmod module_tes.ko

在这里插入图片描述
在这里插入图片描述
测试驱动

cd /sys/class/leds/led1

ls

在这里插入图片描述

cat brightness

在这里插入图片描述

echo 1 > brightness
cat brightness

led灯亮
在这里插入图片描述
再次输入一个其他值,查看结果
在这里插入图片描述

从此处可以看出,cat brightness 实际执行的是s5pv210_led_set()函数

设备驱动模型介绍

  • 设备驱动模型是linux为管理硬件设备和对应驱动制定的一套软件体系。
    class、bus、device、driver、mdev、sysfs等都属于设备驱动模型的范畴

  • 设备驱动模型中总线概念:与物理总线形成对应关系,能更方便的管理硬件设备。总线模型举例:USB总线,Platform总线等

  • 总线中包含设备(struct device)和 驱动(struct driver)

  • class:作为同属一个class的多设备容器,为了对各种设备进行分类管理。

  • 物理上实际存在的总线例如USB、SPI、UART等都有各自的管理方法,那在物理不存在的总线设备设计为Platform总线,也包含设备(struct platform_device)和驱动(struct platform_driver)。

platform

platform_device定义

struct platform_device 
{
	const char	* name;      //平台总线中设备的名字,在平台总线下有多个设备,每个设备都有自己的名称
	int		id;              //设备的排序
	struct device	dev;     //所有设备通用的属性
	u32		num_resources;    //设备资源,如IO等一些外设等的个数
	struct resource	* resource; //设备资源的首地址,和上面的个数num_resources一起构成一个数组来表示这个资源
	const struct platform_device_id	*id_entry;  //设备ID表,表示同一种类型的几个设备的ID号,数组表示。
	struct pdev_archdata	archdata; /* arch specific additions *///用户自定义数据,扩展数据
};

platform_driver定义

struct platform_driver 
{
	int (*probe)(struct platform_device *);     //探测函数,安装设备,初始化设备,并且判断是否能成功(初始化成功,通讯成功等等)
	int (*remove)(struct platform_device *);    //从内核中删除这个设备
	void (*shutdown)(struct platform_device *); // 关闭设备
	int (*suspend)(struct platform_device *, pm_message_t state);  //挂起
	int (*resume)(struct platform_device *);    //唤醒
	struct device_driver driver;          //驱动的通用属性
	const struct platform_device_id *id_table; //设备ID表
};

注册Platform设备函数

int platform_device_register(struct platform_driver *pdev)

注册Platform驱动函数

int platform_driver_register(struct platform_driver *drv)

例程

设备端的结构体定义
static struct S5pv210_led_platdata s5pv210_led1_pdata = {
	.name		= "led1",
	.gpio		= S5PV210_GPJ0(3),
	.flags		= S5pv210_LEDF_ACTLOW | S5pv210_LEDF_TRISTATE,
};

static struct S5pv210_led_platdata s5pv210_led2_pdata = {
	.name		= "led2",
	.gpio		= S5PV210_GPJ0(4),
	.flags		= S5pv210_LEDF_ACTLOW | S5pv210_LEDF_TRISTATE,
};

static struct S5pv210_led_platdata s5pv210_led3_pdata = {
	.name		= "led3",
	.gpio		= S5PV210_GPJ0(5),
	.flags		= S5pv210_LEDF_ACTLOW | S5pv210_LEDF_TRISTATE,
};

struct platform_device s5pv210_led1 = {
	.name   = "s5pv210_led",
	.id     = 1,
	.dev = { .platform_data = &s5pv210_led1_pdata 
	},
};

struct platform_device s5pv210_led2 = {
	.name   = "s5pv210_led",
	.id     = 2,
	.dev = { .platform_data = &s5pv210_led2_pdata 
	},
};

struct platform_device s5pv210_led3 = {
	.name   = "s5pv210_led",
	.id     = 3,
	.dev = { .platform_data = &s5pv210_led3_pdata 
	},
};
static struct platform_device *smdkc110_devices[] __initdata = {

	&s5pv210_led1,
	&s5pv210_led2,
	&s5pv210_led3,
完整驱动代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h> //包含file_operations,register_chrdv,unregister_chrdv声明的头文件
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>		// arch/arm/mach-s5pv210/include/mach/gpio-bank.h
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#include <linux/platform_device.h>
#include <mach/leds-gpio.h>
#include <linux/slab.h>

struct s5pv210_gpio_led {
	struct led_classdev		 cdev;
	struct S5pv210_led_platdata	*pdata;
};

static inline struct s5pv210_gpio_led *pdev_to_gpio(struct platform_device *dev)
{
	return platform_get_drvdata(dev);
}

static inline struct s5pv210_gpio_led *to_gpio(struct led_classdev *led_cdev)
{
	return container_of(led_cdev, struct s5pv210_gpio_led, cdev);
}

static void s5pv210_led_set(struct led_classdev *led_cdev, enum led_brightness value)
{
	struct s5pv210_gpio_led *led = to_gpio(led_cdev);
	struct S5pv210_led_platdata *pd = led->pdata;

	if(value == 1)
	{
		gpio_set_value(pd->gpio , 0);
	}
	else if(value == 0)
	{
		gpio_set_value(pd->gpio , 1);
	}
	else
	{
		printk(KERN_INFO "please input 0 or 1\n");
	}
	
}


static int s5pv210_led_probe(struct platform_device *dev)
{
	struct S5pv210_led_platdata *pdata = dev->dev.platform_data;
	struct s5pv210_gpio_led *led;
	int ret;
	
	led = kzalloc(sizeof(struct s5pv210_gpio_led), GFP_KERNEL);
	if (led == NULL)
	{
		printk(KERN_INFO "No memory for device\n");
		return -ENOMEM;
	}

	platform_set_drvdata(dev, led);

	led->cdev.name = pdata->name;
	led->cdev.brightness = 0;	
	led->cdev.brightness_set = s5pv210_led_set;
	led->pdata = pdata;

	/* register our new led device */

	ret = led_classdev_register(&dev->dev, &led->cdev);
	if (ret < 0) 
	{
		printk(KERN_INFO "led_classdev_register failed\n");
		kfree(led);
		return ret;
	}

	ret = gpio_request(pdata->gpio, pdata->name);		
	if (ret < 0) 
	{
		printk(KERN_INFO "gpio_register failed\n");
	}
	else
	{
		printk(KERN_INFO "gpio_register success\n");
		gpio_direction_output(pdata->gpio, 1);
	}
	
	return 0;
}

static int s5pv210_led_remove(struct platform_device *dev)
{
	struct s5pv210_gpio_led *led = pdev_to_gpio(dev);

	led_classdev_unregister(&led->cdev);
	gpio_free(led->pdata->gpio);
	kfree(led);

	return 0;
}

static struct platform_driver s5pv210_led_driver = {
	.probe		= s5pv210_led_probe,
	.remove		= s5pv210_led_remove,
	.driver		= {
		.name		= "s5pv210_led",
		.owner		= THIS_MODULE,
	},
};

static int __init s5pv210_led_init(void)
{
	return platform_driver_register(&s5pv210_led_driver);
}

static void __exit s5pv210_led_exit(void)
{
	platform_driver_unregister(&s5pv210_led_driver);
}

module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");				// 描述模块的许可证
MODULE_AUTHOR("ww");				// 描述模块的作者
MODULE_DESCRIPTION("module test");	// 描述模块的介绍信息
MODULE_ALIAS("alias xxx");			// 描述模块的别名信息

自动创建设备节点的驱动文件和应用层文件编写

相关函数介绍

  • 前期使用mknod命令手动创建设备节点相对繁琐,Linux内核有一组函数,可以用来在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点。
  • 自动创建设备文件节点给一些设备驱动,那么就要将这些设备驱动给归到一个class设备驱动类中
  • 在编写代码前,先介绍相关函数
(1)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
@description : 自动注册一个范围的设备号
@dev		 : dev_t结构体地址
@baseminor: 起始的次设备号
@count 	 :次设备号个数
@name	 :设备名称
(2)
MAJOR(dev_t devid)
@description :获取主设备号
(3)
MINOR(dev_t devid)
@description :获取次设备号
(4)
MKDEV(int major, int minor)
@description :合成设备号
@major:主设备号
@minor:次设备号
(5)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
@description : 根据定义的设备号注册
@from :要分配的设备编号范围的初始值,需明确主设备号和起始次设备号
@count:次设备号个数
@name:相关联的设备名称.(可在/proc/devices目录下查看到), 也即本组设备的驱动名称
(6)
void cdev_init(struct cedv *cdev,const struct file_operations *fops)
@description : 初始化cdev结构体
@cdev:待初始化的cdev结构
@fops:设备对应的操作函数集
(7)
int cdev_add(struct cdev *p,dev_t dev,unsigned count)
@description :注册字符驱动设备
@p:待添加到内核的字符设备结构,要注册的字符设备
@dev:设备号,驱动程序对应的主设备号
@count:添加的设备个数
(8)
struct class *class_create(struct module *owner, const char *name)
@description: 创建一个class类型的对象
@owner	: THIS_MODULE
@name	: 类名字
(9)
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
@description	: 生成设备节点并导出到用户空间
@class	: 指向该设备应注册到的struct class的指针(上一个函数)
@parent	: 指向此新设备的父struct设备的指针(如果有),没有填NULL
@devt	: 要添加的char设备的dev_t
@drvdata	: 要添加到设备中以进行回调的数据(如果有),没有填NULL
@fmt		: 设备名称的字符串
(10)
void device_destroy(struct class *dev, dev_t devt)
@description :移除设备
@dev:要删除的设备所在的类名
@devt:要删除的设备号
(11)
void class_destroy(struct class *cls)
@description :删除类
@cls:要删除的类
(12)
void unregister_chrdev_region(dev_t from, unsigned count)
@description :删除设备号列表中的元素
@from:要删除的设备
@count:要卸载的元素个数
(13)
int cdev_del(struct cdev *p)
@description :注销字符设备
@p:要注销的字符设备

驱动文件编写

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/uaccess.h>

#include <linux/leds.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <mach/gpio.h>

#define OFF 0
#define ON 1
#define ALL_OFF 3
#define READ_LED 4
#define ALL_ON 5

#define LED1 S5PV210_GPJ0(3)
#define LED2 S5PV210_GPJ0(4)
#define LED3 S5PV210_GPJ0(5)

#define DEVICE_NAME "led_module"

static int MYMAJOR;//主设备号
static struct cdev led_cdev;//字符设备
static struct class *led_class;//类名
static dev_t devid;//设备号

//上层应用启用该驱动时的打印信息
static int led_open(struct inode *inode, struct file *file)
{
	printk("led_open success\n");
	return 0;
}

//上层应用关闭该驱动时的打印信息
static int led_close(struct inode *inode, struct file *file)
{
	printk("led_close success\n");
	return 0;
}

//上层应用传数据到内核空间
static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
	int value = 0;
	
	switch(cmd)
	{
		case OFF:
			if(arg == 1)
				gpio_set_value(LED1, 1);
			else if(arg == 2)
				gpio_set_value(LED2, 1);
			else if(arg == 3)
				gpio_set_value(LED3, 1);
			else
		break;
		case ON:
			if(arg == 1)
				gpio_set_value(LED1, 0);
			else if(arg == 2)
				gpio_set_value(LED2, 0);
			else if(arg == 3)
				gpio_set_value(LED3, 0);
			else
		break;
		case ALL_ON:
			gpio_set_value(LED1, 0);
			gpio_set_value(LED2, 0);
			gpio_set_value(LED3, 0);
		break;
		case ALL_OFF:
			gpio_set_value(LED1, 1);
			gpio_set_value(LED2, 1);
			gpio_set_value(LED3, 1);
		break;
		case READ_LED:
			if(arg == 1)
				value = gpio_get_value(LED1);
			else if(arg == 2)
				value = gpio_get_value(LED2);
			else if(arg == 3)
				value = gpio_get_value(LED3);
			else
		break;
		default:
			return -EINVAL;
		break;
	}
	return value;
}

//定义文件描述符
static const struct file_operations test_fops = {
	.owner	 = THIS_MODULE,
	.open	 = led_open,
	.ioctl	 = led_ioctl,
	.release = led_close
};

//驱动注册函数
//test_module为insmod生成的驱动名
static int __init led_init(void)
{
	printf("led_init ing\n");
	if(MYMAJOR == 0)//如果没有定义主设备号
	{
		alloc_chrdev_region(&devid, 0, 1, DEVICE_NAME);//自动分配设备号
		MYMAJOR = MAJOR(devid);//获取主设备号
	}
	else//如果定义了主设备号
	{
		devid = MKDEV(MYMAJOR, 0);//创建设备号
		register_chrdev_region(devid, 1, DEVICE_NAME);//根据定义的设备号注册
	}

	cdev_init(&led_cdev, &led_fops);//初始化cdev
	cdev_add(&led_cdev, devid, 1);//添加cdev

	led_class = class_create(THIS_MODULE, DEVICE_NAME);//创建类
	device_create(led_class, NULL, devid, NULL, DEVICE_NAME);//生成设备

	gpio_request(LED1, "GPJ0");
	gpio_request(LED2, "GPJ0");
	gpio_request(LED3, "GPJ0");

	gpio_direction_output(LED1, 1);
	gpio_direction_output(LED2, 1);
	gpio_direction_output(LED3, 1);
	
	return 0;
}

//驱动卸载函数
static void __exit led_exit(void)
{
	printk("led_exit success\n");
	gpio_free(LED1);
	gpio_free(LED2);
	gpio_free(LED3);
	device_destroy(led_class, devid);
	clss_destroy(led_class);
	unregister_chrdev_region(devid, 1);
	cdev_del(&led_cdev);
}

module_init(led_init);//驱动注册
module_exit(led_exit);//驱动卸载

MODULE_LICENSE("GPL");//注意一定要有,否则会出未知问题
MODULE_AUTHOR("WuLi");

Makefile文件编写

KERN_VER = $(shell uname -r)
KERN_DIR = /home/x210v3/kernel/ #目录为kernel的源码树目录

obj-m += led_test.o

all:
	make -C $(KERN_DIR) M='pwd' modules

cp:
	cp *.ko /home/x210v3/rootfs/customer/
#复制到使用busybox创建的rootfs文件夹

.PHONY: clean
clean:
	make -C $(KERN_DIR) M='pwd' modules clean

上层应用程序编写

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define FILE "/dev/led_module"//与驱动文件中的DEVICE_NAME一致

#define OFF 0
#define ON 1
#define ALL_OFF 3
#define READ_LED 4
#define ALL_ON 5

char buf[20];//用户空间

int main(void)
{
	int fd = -1;
	int val = 0;

	fd = open(FILE, O_RDWR);
	if(fd < 0)
	{
		printf("open %s fail\n",FILE);
		return -1;
	}
	printf("open %s success\n",FILE);

	while(1)
	{
		memset(buf, 0, sizeof(buf));
		printf("Please input onx | offx | allon | alloff | readledx | quit\n");
		scanf("%s",buf);

		if(!strcmp(buf, "off1"))
			ioctl(fd, OFF, 1);
		else if(!strcmp(buf, "off2"))
			ioctl(fd, OFF, 2);
		else if(!strcmp(buf, "off3"))
			ioctl(fd, OFF, 3);
		
		else if(!strcmp(buf, "on1"))
			ioctl(fd, ON, 1);
		else if(!strcmp(buf, "on2"))
			ioctl(fd, ON, 2);
		else if(!strcmp(buf, "on3"))
			ioctl(fd, ON, 3);
		
		else if(!strcmp(buf, "allon"))
			ioctl(fd, ALL_ON, 0);
			
		else if(!strcmp(buf, "alloff"))
			ioctl(fd, ALL_OFF, 0);
		
		else if(!strcmp(buf, "readled1"))
		{
			val = ioctl(fd, READ_LED, 1);
			if(val == 0)
				printf("LED1 is ON\n");
			else
				printf("LED1 is OFF\n");
		}
		else if(!strcmp(buf, "readled2"))
		{
			val = ioctl(fd, READ_LED, 2);
			if(val == 0)
				printf("LED2 is ON\n");
			else
				printf("LED2 is OFF\n");
		}
		else if(!strcmp(buf, "readled3"))
		{
			val = ioctl(fd, READ_LED, 3);
			if(val == 0)
				printf("LED3 is ON\n");
			else
				printf("LED3 is OFF\n");
		}
		else if(!strcmp(buf, "quit"))
			break;
		else
		
	}
	close(fd);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值