第一个驱动步骤如下:
1、理解驱动从应用到内核应该有哪些步骤,如下图
app 应用 -》 调用c库 -> c库通过swi触发不同的软中断 调用不同的系统接口->系统接口根据 打开的文件属性调用对应的驱动(VFS干的活)
这里的对应驱动如何调用可以先不管;
以led驱动举例:
步骤是: 打开对应的led设备文件,向设备文件中写入值 开 或者 关灯;
应用程序如下
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/*
输入 on 表示开灯
输入 off 表示关灯
*/
int main(int argc, char **argv)
{
int fd = 0;
int val = 1;
if (argc < 2)
{
printf("Usage: \n");
printf(" led <on/off> \n");
}
fd = open("/dev/led", O_RDWR);
if (fd < 0)
{
printf("/dev/xxx file open failed \n");
return -1;
}
if (strcmp(argv[1], "on") == 0)
{
val = 0;
}else if(strcmp(argv[1], "off") == 0)
{
val = 1;
}else
{
printf("input error \n");
}
write(fd, &val, 4);
}
驱动框架:
1\写出 led_open led_write 对应的操作,供我们应用层调用。
2\怎么告诉内核?
答:
a、定义一个file_operations 结构并填充
b、告诉内核 register_chrdev() 使用这个函数
c、谁来调用它? 这就是驱动入口函数:
如有三个驱动程序:
first_drv_init
second_drv_init
third_drv_init
.....
内核怎么知道对应关系?
我们通过统一的接口调用 module_init(驱动入口函数);module_exit(驱动卸载接口函数)
修饰入口函数 module_init 定义一个结构体,结构中一个函数指针指向入口函数
app : open , read write
app : open ("/dev/xxx") 这个设备文件属性如上图
应用 程序如何找到对应的驱动调用呢?
用设备类型 + 主设备号 +此设备号 找到对应的file_operations 结构
chrdev数组: 0 1 2 3 4 5 6 7 8 9 。。。。
以major作为索引 挂载对应file_operations结构
驱动程序如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/pci.h>
#include <linux/poll.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/soundcard.h>
#include <linux/ac97_codec.h>
#include <linux/sound.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#define GPFCON 0x56000050
#define GPFDAT 0x56000054
static struct class * first_drv_class;
static struct class_device * first_dev_class;
volatile unsigned int * gpfcon = NULL;
volatile unsigned int * gpfdata = NULL;
//对应硬件的操作
static int first_drv_open(struct inode * node , struct file * file)
{
printk("first_drv_open\n");
/* 设置为输出模式 4,5,6 */
*gpfcon &= ~(0x3<<8)|(0x3<<10)|(0x3<<12);
*gpfcon |= (0x1<<8)|(0x1<<10)|(0x1<<12);
return 0;
}
//ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
//ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
static int first_drv_write(struct file * fp, const char __user * buf, int len, loff_t * offset)
{
int val = 0;
copy_from_user(&val, buf, len);
if(val == 0)
{
/* 点灯 */
*gpfdata &= ~((0x1<<4)|(0x1<<5)|(0x1<<6));
}else
{
/* 关灯 */
*gpfdata |= ((0x1<<4)|(0x1<<5)|(0x1<<6));
}
return 0;
}
//定义file_operations 结构
struct file_operations first_drv_ops = {
.owner = THIS_MODULE,
.open = first_drv_open,
.write = first_drv_write
};
int major = 0;
//你驱动的入口函数
int first_drv_init(void)
{
//用 file_operations和主设备号去注册字符设备驱动
major = register_chrdev(0, "led", &first_drv_ops);
//这个是根据sys/下信息来自动创建设备文件的
first_drv_class = class_create(THIS_MODULE, "led");
first_dev_class = class_device_create(first_drv_class, NULL, MKDEV(major, 0), NULL, "led");
//这里已经在跑系统了,所以要使用虚拟地址(这就是地址映射)
gpfcon = (volatile unsigned int *)ioremap(GPFCON, 8);
gpfdata = gpfcon + 1;
return 0;
}
void first_drv_exit(void)
{
unregister_chrdev(major, "led");
class_device_unregister(first_dev_class);
class_destroy(first_drv_class);
iounmap(gpfcon);
iounmap(gpfdata);
}
//驱动通用接口,告诉内核你写的驱动
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");