前面记录了根文件系统,现在开始写驱动程序,关于框架什么的都是一些概念的东西,这里就不详述,先写一个最简单的LED驱动
从现在开始进入Linux驱动的大门,如何自已写,参考内核自带的字符驱动,仿照写出自已的驱动,这些驱动只适合自已使用,想
做成通用的驱动,后面会慢慢深入,现在先了解和熟悉
linux驱动有以下几个组成
1:file_operations 结构体
包含open write read 等操作函数
2:入口和出口函数
入口 注册驱动 创建类 类下创建设备 地址映射
出口 同入口相反 卸载驱动 类 类设备 取消地址映射
3:功能操作函数
当应用程序/测试程序 执行对就函数时,就会调用到具体的操作函数,在这里函数里实现具体功能实现
下面看代码比较直观:参考其它驱动和不断调试修改的最终代码
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/ioport.h>
#include <linux/fcntl.h>
#include <linux/mc146818rtc.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/sysctl.h>
#include <linux/wait.h>
#include <linux/bcd.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/ratelimit.h>
#include <linux/bfs_fs.h>
#include <asm/current.h>
static struct class *leddev_class; /* 定义一个类用于自动创建设备节点 */
static struct device *dev_led;
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
/* 测试程序执行open 时会调用这个函数
* 这个函数实现 GPIO的设置
*/
static int led_dev_open(struct inode *inode , struct file *file)
{
printk("open function test!
");
*gpfcon &= ~ ((0x03 << 8) | (0x03 << 10) | (0x03 << 12));
*gpfcon |= ((0x01 << 8) | (0x01 << 10) | (0x01 << 12));
return 0;
}
/* 测试程序执行write时 调用驱动的这个函数
* copy_from_user 拷贝测试程序的数据到驱动
* 根据数据执行对就的硬件操作
*/
static int led_dev_write(struct file *file, char __user *buf,size_t count, loff_t *ppos)
{
int val;
copy_from_user(&val,buf,count); //copy_to_user;
if(val == 1)
{
*gpfdat &= ~((1 << 4) | (1 << 5) | (1 << 6));
printk("led1 led2 led3 on!
");
}
else
{
*gpfdat |= ((1 << 4) | (1 << 5) | (1 << 6));
printk("led1 led2 led3 off!
");
}
return 0;
}
/* 定义一个file_operations结构体
* 实现 open write 两个函数
*/
static struct file_operations led_dev_per = {
.owner = THIS_MODULE,
.open = led_dev_open,
.write = led_dev_write,
};
/* 驱动程序入口函数 入口函数 注册字符设备 装载驱时调用
* 创建类 并在类下创建设备 用于mdev 自动创建设备节点
* 寄存器地址映射 用虚拟地址操作GPIO
*/
static int major; /* 定义一个变量用于主设备号 */
static int led_dev_init(void)
{
major = register_chrdev(0, "led_dev", &led_dev_per);
leddev_class = class_create(THIS_MODULE, "led_dev");
dev_led = device_create(leddev_class, NULL, MKDEV(major,0), NULL, "leds");
gpfcon = (volatile unsigned long *)ioremap(0x56000050,16);
gpfdat = gpfcon + 1;
return 0;
}
/* 驱动程序出口函数 卸载驱动时调用
* 卸载字符设备 卸载类设备 卸载类
* 取消寄存器地址映射
*/
static void led_dev_exit(void)
{
unregister_chrdev(major, "led_dev");
device_destroy(dev_led, MKDEV(major,0));
class_destroy(leddev_class);
iounmap(gpfcon);
}
/* 入口函数和出口函数需要用下面的宏修饰
* 还需要加入 "GPL" 协议
*/
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
下面列出测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc,char **argv)
{
int fd;
int val = 1;
fd = open("/dev/leds",O_RDWR); /* 打开设备/dev/leds 可读可写 */
if(fd < 0) /* 判断打开是否成功 */
printf("can't open this dev!
"); /* 失败就打印这条信息 */
if(argc != 2) /* 判断串口输入参数 不等于2时 */
{
printf("Usage:
"); /* 打印使用帮助信息 */
printf(" %s <on/off>
",argv[0]);
}
if(strcmp(argv[1],"on") == 0) /* 判断第二个参数 等ON 打开LED */
val = 1;
else if(strcmp(argv[1],"off") == 0) /* 判断第二个参数 等OFF 关闭LED */
val = 0;
write(fd,&val,4); /* 把值发送给驱动程序 */
return 0;
}
这里列出Makefile
KERN_DIR = /work/linux/linux-3.4.2
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += led_char_dev.o
驱动个人认为主要是记住需要的东西和流程即可,内核如何操作,后面会学到