在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
實驗現象:看見開發板上的燈爲流水燈。當隨便按下一個按鍵時,蜂鳴器會響,在按一次時,蜂鳴器翻轉不響,按鍵是用中斷的方式來觸發。