字符设备驱动开发
字符设备驱动
定义
以字节为单位进行操作,如:(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;
}