下面是关于字符驱动两个程序,主要是说明驱动编写的思想,理解驱动是怎么一步一步被实现的。
驱动的第一个实现程序,是相对于裸机编程的,主要是体会一下驱动编程思想:
cdev.h: 所包含的头文件
#ifndef CDEV_H_
#define CDEV_H_
#define MAX_CDEVS 1024
struct cdev {
struct file_operations *ops;
char *name;
int id;
};
fs.h : 包含的另一个头文件
#ifndef FS_H_
#define FS_H_
struct file_operations {
void (*open)(void);
void (*read)(void);
void (*write)(void);
void (*close)(void);
};
void open(int id);
void read(int id);
void write(int id);
void close(int id);
#endif /* FS_H_ */
extern struct cdev *cdev_map[MAX_CDEVS];
#endif /* CDEV_H_ */
cdev.c:
#include "cdev.h"
struct cdev *cdev_map[MAX_CDEVS];
void (struct cdev *dev, struct file_operations *fops)
{
dev->ops = fops;
}
void cdev_add(struct cdev *dev, int id)
{
cdev_map[id] = dev;
}
fs.c: 这里面是具体函数的实现,通过调用结构体执行不同的功能
/*
* fs.c
*
* Created on: 2015-4-3
* Author: Administrator
*/
#include "cdev.h"
#include "fs.h"
void open(int id)
{
struct cdev *tmp_dev = cdev_map[id];
tmp_dev->ops->open();
}
void write(int id)
{
struct cdev *tmp_dev = cdev_map[id];
tmp_dev->ops->write();
}
void read(id)
{
struct cdev *tmp_dev = cdev_map[id];
tmp_dev->ops->read();
}
void close(int id)
{
struct cdev *tmp_dev = cdev_map[id];
tmp_dev->ops->close();
}
led.c: 驱动功能的实现,将特定的功能传给结构体,并给一个标号,方便后面查找函数和调用
/*
* led.c
*
* Created on: 2015-4-3
* Author: Administrator
*/
#include "fs.h"
#include "cdev.h"
#define GPG3CON (*(volatile unsigned int *)0xe03001c0)
#define GPG3DAT (*(volatile unsigned int *)0xe03001c4)
void led_init(void)
{
GPG3CON = 0x1111;
}
void led_on(void)
{
GPG3DAT = 0xf;
}
void led_off(void)
{
GPG3DAT = 0x0;
}
struct file_operations led_ops = {
.open = led_init,
.write = led_on,
.close = led_off,
};
struct cdev led_cdev;
int led_dev_num = 0; //led设备的设备号
void led_driver_init(void)
{
//初始化led 字符设备驱动
cdev_init(&led_cdev, &led_ops);
//注册进内核
cdev_add(&led_cdev, led_dev_num);
}
kernel_main.c 主函数调用写好的驱动
/*
* kernel_main.c
*
* Created on: 2015-4-3
* Author: Administrator
*/
void start_kernel(void)
{
//内核各种机制的初始化以及模块入口初始化
led_driver_init();
//运行用户空间进程(应用)
main();
}
main.c 主函数:
#include "cdev.h"
#include "fs.h"
/*
* 延时函数
*/
void soft_delay(unsigned int loops)
{
while (loops > 0)
loops--;
}
/*
* led 测试c入口
*/
void main()
{
//获取led的设备号 0
open(0);
write(0);
}
对应于上面程序的makefile:
##############################################################
CROSS_COMPILE = arm-none-eabi-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
INCLUDE_PATH = ./include
CFLAGS = -I$(INCLUDE_PATH) -mabi=apcs-gnu -c -g -O0 -o
##############################################################
objs = init/start.o kernel/kernel_main.o cdev/fs.o cdev/cdev.o drivers/led.o main.o
pro_name = kernel
##############################################################
all:$(objs)
$(LD) $(objs) -Tmap.lds -o $(pro_name).elf #链接
$(OBJCOPY) -O binary $(pro_name).elf $(pro_name).bin #二进制提取
$(OBJDUMP) -D $(pro_name).elf > $(pro_name).dis #反汇编
%.o : %.s
$(CC) $(CFLAGS) [email protected] $< #-c 只编译不链接 -g 调试 -O0优化等级 -O1 -O2 -o 输出
%.o : %.c
$(CC) $(CFLAGS) [email protected] $< #[email protected]:目标 $<:第一个依赖 $^: 多个依赖
clean:
rm -rf *.bin *.elf *.dis $(objs)
驱动的第二个实现,这个是在内核里面编写的程序:
hello_char.c:这个C程序里面包含了很多内核里面的语句,这个要查一下他们的用法
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
struct cdev hello_cdev; 先创建一个对象
int hello_major = 250; 主设备号 (前12位)
int hello_minor = 0; 次设备号 (后20位)
int num_of_hellos = 1; 注册这个设备的个数,这里表示就一个
dev_t hello_devno; 最终构成的设备号记做hello_devno
MODULE_LICENSE("GPL");
static int hello_open(struct inode *nodp, struct file *filp)
{
printk(KERN_INFO "hello open! \n");
return 0;
}
static int hello_release(struct inode *nodp, struct file *filp)
{
printk(KERN_INFO "hello release! \n");
return 0;
}
struct file_operations hello_ops = {
.open = hello_open,
.release = hello_release,
};
static int __init hello_init(void)
{
/*construct cdev number*/
hello_devno = MKDEV(hello_major, hello_minor); //hello_major << 20 | hello_minor; 构成设备号
register_chrdev_region(hello_devno, num_of_hellos, "hello_char");注册设备号,后面三个参数分别表示(设备号,有几个,设备名字)
/*cdev initialize*/
cdev_init(&hello_cdev, &hello_ops); 初始化设备
/*add to kernel*/
cdev_add(&hello_cdev, hello_devno, num_of_hellos); 将设备号和设备联系起来
printk(KERN_INFO "hello_init ! \n");
return 0;
}
static void __exit hello_exit(void) 卸载的函数
{
cdev_del(&hello_cdev); 卸载的时候删除对象
unregister_chrdev_region(hello_devno, num_of_hellos);
printk("hello_exit ! \n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("just for test!");
test.c内核调用驱动的程序:
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, const char *argv[])
{
int fd;
fd = open("/dev/hello", O_RDWR); 打开设备,在这之前linux里面创建了一个节点->sudo mknod /dev/hello c 250 0 对应前面的主次设备号
if (fd < 0) {
perror("open");
return -1;
}
close(fd);
return 0;
}
对应于上面程序的makefile :
MOD_NAME = hello_char
obj-m = $(MOD_NAME).o
#KERN_DIR = /home/linux/linux-2.6.35-farsight
KERN_DIR = /lib/modules/$(shell uname -r)/build
all:
make -C $(KERN_DIR) M=$(shell pwd) modules
clean:
rm -rf *.o *.ko *.mod.c *.order *.symvers .*.cmd .*versions
backup:
tar cvf ../$(MOD_NAME).tar.gz ../$(MOD_NAME)
cp ../$(MOD_NAME).tar.gz /mnt/hgfs/ubuntu_share