题目有点大,好吧,我要记录的确实是我刚开始学写驱动的流程;这里我不会写怎么写Kconfig和配置menuconfig生成.config(本文所讲的驱动特指基于linux的字符设备驱动),
我这里要写的是将驱动程序作为单独模块,手动加载的过程(学习过程)。
- 1.写驱动程序的入口函数、出口函数及其修饰,形如:
//入口
static int xxx_init()
{
……
return 0;
}
//出口
static void xxx_exit()
{
……
}
//修饰
module_init(xxx_init);
module_init(xxx_exit);
- 2.申请/释放设备号
设备号的操作函数:
提取主设备号
主设备号 = MAJOR(dev_t dev_id);
提取次设备号
次设别号 = MINOR(dev_t dev_id);
合并主次设备号
dev_t dev_id = MKDEV(主设备号,次设备号);
分配相关函数:
register_chrdev_region //静态分配
alloc_chrdev_region //动态分配
unregister_chrdev_region //释放设备号资源
- 3.注册/释放cdev结构
1)分配cdev结构
static struct cdev cdev;
2)分配file_operations结构
static struct file_operations led_fops = {
.owner = THIS_MODULE,
…… //关联驱动接口函数
};
3)将file_operations填充到cdev
cdev_init(&cdev, &led_fops);
4)注册cdev
cdev_add(&cdev, dev_id, 1);
5)释放cdev
cdev_del(&cdev);
- 4.写具体设备驱动
参照file_operations提供的驱动接口编写驱动程序。
- 5.编写编译驱动模块的Makefile
MODNAME=xxx #模块名
KERNELDIR=...... #内核路径
obj-m += $(MODNAME).o
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean
- 6.编译加载内核
make编译 生成.ko模块;
insmod xx.ko --加载模块
rmmod xx --卸载模块
cat /proc/devices --查看模块设备号
知道设备号后,创建设备节点文件(linux下一切皆文件,用户通过设备文件访问设备)
mknod /dev/设备名 c 主设备号 次设备号;
- 7.编写测试用应用程序
应用程序open上面创建的设备文件即可访问模块所驱动的设备(需交叉编译后copy到开发板根文件系统)。
- 一个小例子:
//led_drv.c 驱动程序//
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
static int major = 0; //主设备号
static int minor = 0; //次设备号
//分配cdev
static struct cdev cdev;
//定义led_open
static int led_open(struct inode *inode,
struct file *file)
{
printk("%s\n", __FUNCTION__);
//设置GPIO为输出然后输出1
return 0;
}
//定义led_close
static int led_close(struct inode *inode,
struct file *file)
{
printk("%s\n", __FUNCTION__);
//设置GPIO为输入
return 0;
}
//分配初始化led_fops
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close,
};
static int char_drv_init(void)
{
int retval;
dev_t dev_id;
/*1. 分配设备号*/
if (major) {
//合并主次设备号
dev_id = MKDEV(major, minor);
//静态分配
retval = register_chrdev_region(dev_id,
1,
"my_char");
} else {
//动态分配
retval = alloc_chrdev_region(&dev_id,
0, 1, "my_char");
//提取主设备号
major = MAJOR(dev_id);
printk("major = %d, minor = %d\n", major,
minor);
}
if (retval) {
printk("register my char device failed.\n");
return -1;
}
/*2. 分配,初始化cdev*/
/*2.1 填充struct file_operations*/
cdev_init(&cdev, &led_fops);
/*3. 注册cdev*/
/*3.1 初始化设备号,设备个数然后注册*/
cdev_add(&cdev, dev_id, 1);
/*4. GPIO资源申请*/
return 0;
}
static void char_drv_exit(void)
{
dev_t dev_id = MKDEV(major, minor);
/*释放cdev*/
cdev_del(&cdev);
//释放设备号资源
unregister_chrdev_region(dev_id, 1);
}
module_init(char_drv_init);
module_exit(char_drv_exit);
MODULE_LICENSE("GPL v2");
led_test.c 测试用应用程序//
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
fd = open("/dev/myled", O_RDWR);//访问设备
if (fd < 0) {
printf("open led device failed.\n");
return -1;
}
sleep(2);
close(fd);//结束访问
return 0;
}