在Linux2.6以上的设备驱动模型中,有三大实体:总线、设备和驱动。总线负责将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个一个驱动的时候,会寻找与之匹配的设备,匹配有总线完成。
总线、设备、驱动的这三者的关系:
总线负责将设备和驱动绑定,一个现实的Linux设备和驱动通常都需要挂接在一种总线上,如USB、I2C、SPI接口的设备都是有对应的总线来管理,通过总线来操作设备。设备与驱动需要挂载在总线上,需要指明驱动与设备是属于哪条总线的,所以设备与驱动需要注册。而总线在linux系统中也是属于设备,所以总线也要注册,同时要先有总线而后才能注册设备和驱动,所以总线要先注册。总线在linux系统中有俩种,一是实际存在的总线 pci usb 等等,还有一类是虚拟存在的总线 platform ,platform总线主要是用于集成在SoC系统的设备,使得每一个设备都属于一条总线,相应的设备称为platform_device,而驱动成为 platform_driver。
tips:一个驱动可以对应多个设备,一个设备只能有一个驱动。
Platform模型分层模型
1.分为两部分,设备信息和驱动程序。
2.如果要设备能正常工作,就必须把设备信息及驱动程序都注册进内核。
3.设备信息和驱动程序是分别写成独立的ko文件(也可以写成一个ko)
不管先装驱动还是先安装驱动,内核都正确的进行绑定设备和驱动的匹配绑定。
内核为设备找对应的驱动,以及内核为驱动找对应的设备是通过比较它们的name成员所指的字符串是否相同。
写了一个platform来控制led.
device.c代码清单:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
struct resource my_resource[] = {
[0] = {
.start = 0x110002e0,//物理地址
.end = 0x110002e7,
.name = "LED",
.flags = IORESOURCE_MEM,//资源的类型,为内存
},
[1] = {
.start = 0x114000A0,
.end = 0x114000A7,
.name = "BEEP",
.flags = IORESOURCE_MEM,//资源的类型,为内存
},
};
static void dev_release(struct device *dev)
{
;
}
struct platform_device my_device={
.name = "test",
.id = -1,
.num_resources = ARRAY_SIZE(my_resource),//资源个数
.resource = my_resource, //设备占用资源的首地址
.dev ={
.release = dev_release,
},
};
static __init int tiny4412_device_init(void)
{
int ret;
ret = platform_device_register(&my_device);//设备注册
if(ret < 0){
printk("my device register error\n");
}
return 0;
}
static __exit void tiny4412_device_exit(void)
{
platform_device_unregister(&my_device);//设备注销函数
}
module_init(tiny4412_device_init);
module_exit(tiny4412_device_exit);
MODULE_LICENSE("GPL");
driver.c代码清单:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <asm/io.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <linux/miscdevice.h>
#define KEY_GPIO(i) EXYNOS4_GPX3(2+i)
int key_irq[4]={0};
//static struct platform_device *my_device;
struct resource *led_resource;
struct resource *beep_resource;
unsigned int *vir_gpm4con;
unsigned int *vir_gpm4dat;
unsigned int *vir_gpd0con;
unsigned int *vir_gpd0dat;
irqreturn_t key_irq_handle(int irq, void *args)//按键中断函数
{
int i=0;
static int num = 0;
if(num==2){
for(i = 0; i < 4; i++){
if(irq==key_irq[i]){
printk("key%d Down!\n", i+1);
*vir_gpm4dat ^= (0xf);
*vir_gpd0dat ^= (0x1);
}
}
num=0;
}
num++;
return IRQ_HANDLED;
}
static int led_open(struct inode *i_node, struct file *p_file)
{
printk("%s\n", __func__);
*vir_gpm4con &= ~((0xF<<4*0)|(0xF<<4*1)|(0xF<<4*2)|(0xF<<4*3));
*vir_gpm4con |= (0x1<<4*0)|(0x1<<4*1)|(0x1<<4*2)|(0x1<<4*3);
*vir_gpm4dat |= (0x1<<0)|(0x1<<1)|(0x1<<2)|(0x1<<3);
return 0;
}
//驱动程序的 close 函数
static int led_release(struct inode *i_node, struct file *p_file)
{
printk("%s\n", __func__);
return 0;
}
//驱动程序的 write 接口
static ssize_t led_write(struct file *i_node,const char __user *buf,size_t count, loff_t *off)
{
int i = 0,ret = 0;
char buff[4] = {0};
printk("%s\n", __func__);
ret = copy_from_user(buff,buf,count);
if(ret < 0){
printk("copy_from_user is fail!!\n");
return -1;
}
for(i=0;i<sizeof(buff);i++)
{
if(buff[i] == 0){
*vir_gpm4dat |= (1<<i);
printk("led[%d] is off\n",i);
}
else if(buff[i] == 1){
*vir_gpm4dat &= ~(1<<i);
printk("led[%d] is on\n",i);
}
else{
printk("on/off led err!!\n");
return -1;
}
}
return 0;
}
//文件操作集合
static struct file_operations f_ops ={
.open = led_open,
.release = led_release,
.write = led_write,
};
// 杂项设备核心结构
static struct miscdevice misc_led = {
.minor = MISC_DYNAMIC_MINOR,
.name = "led",
.fops = &f_ops,
};
static int my_probe(struct platform_device *pdev)
{
int i,ret;
int size;
char name[16];
printk("%s\n", __func__);
//my_device = pdev;
led_resource = platform_get_resource(pdev,IORESOURCE_MEM,0);//获取资源
beep_resource = platform_get_resource(pdev,IORESOURCE_MEM,1);
if(led_resource == NULL || beep_resource == NULL){
printk("platform_get_resource error\n");
return -1;
}
size = led_resource->end - led_resource->start; //结束-开始就是字节数
vir_gpm4con = ioremap(led_resource->start, size);
size = beep_resource->end - beep_resource->start; //结束-开始就是字节数
vir_gpd0con = ioremap(beep_resource->start, size);
if(vir_gpm4con == NULL && vir_gpd0con){
printk("ioremap error\n");
return -1;
}
for(i=0; i<4; i++){
memset(name, 0, sizeof(name));
sprintf(name, "key%d_irq", i);
key_irq[i] = gpio_to_irq(KEY_GPIO(i));
ret = request_irq(key_irq[i], key_irq_handle, IRQF_DISABLED | IRQF_TRIGGER_FALLING, name, (void*)(i));
if(ret < 0){
printk("request irq error\n");
goto err_irq;
}
}
ret = misc_register(&misc_led);
if(ret<0){
printk("misc_register is fail!!\n");
return ret;
}
vir_gpm4dat = vir_gpm4con + 1;
vir_gpd0dat = vir_gpd0con + 1;
*vir_gpm4con = 0x1111;
*vir_gpd0con |= 0x1;
*vir_gpm4dat |= 0xf;
*vir_gpd0dat &= ~(0x1);//操作地址
return 0;
err_irq:
while(i--){
free_irq(key_irq[i], NULL);
}
return ret;
}
static int my_remove(struct platform_device *pdev)
{
printk("%s\n", __func__);
return 0;
}
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "test",
.owner = THIS_MODULE,
},
};
static __init int tiny4412_driver_init(void)
{
int ret=0;
ret = platform_driver_register(&my_driver);//注册
if(ret < 0){
printk("driver register error\n");
return ret;
}
return 0;
}
static __exit void tiny4412_driver_exit(void)
{
int i = 4;
iounmap(vir_gpm4con);
iounmap(vir_gpd0con);
while(i--){
free_irq(key_irq[i], NULL);
}
platform_driver_unregister(&my_driver);//销毁
misc_deregister(&misc_led);
}
module_init(tiny4412_driver_init);
module_exit(tiny4412_driver_exit);
MODULE_LICENSE("GPL");
应用层测试代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char *argv[])
{
int fd;
char buf[4]={0,1,0,1};
if(argc <2){
printf("usage : %s <file name >\n",argv[0]);
return -1;
}
fd = open(argv[1],O_RDWR);
if(fd<0){
printf("fd = %d,open %d file is fail!!\n",fd,argv[1]);
return -1;
}
write(fd,buf,4);
sleep(2);
while(1)
{
memset(buf, 0, 4);
buf[0]=1;
write(fd,buf,4);
sleep(2);
memset(buf, 0, 4);
buf[1]=1;
write(fd,buf,4);
sleep(2);
memset(buf, 0, 4);
buf[2]=1;
write(fd,buf,4);
sleep(2);
memset(buf, 0, 4);
buf[3]=1;
write(fd,buf,4);
sleep(2);
}
return 0;
}
Makefile
obj-m += device.o driver.o
KERN_DIR=/root/work/tiny4412/linux/linux-3.5
PWD := $(shell pwd)
modules:
$(MAKE) ARCH=arm -C $(KERN_DIR) M=$(PWD) modules
arm-linux-gcc app_led_test.c -o app_led_test
cp app_led_test device.ko driver.ko /root/work/tiny4412/rootfs/root_nfs/root/
clean:
$(MAKE) ARCH=arm -C $(KERN_DIR) M=$(PWD) modules clean
完成后,在Linux中make一下,把生成的两个.ko都insmod到内核中,运行app,输入./app_led_test /dev/led
实验现象:看见开发板上的灯为流水灯。当随便按下一个按键时,蜂鸣器会响,在按一次时,蜂鸣器翻转不响,按键是用中断的方式来触发。