linux設備驅動模型

1、首先回顧下之前寫的驅動和數據在一起的led驅動代碼,代碼如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <asm/io.h>	//ioremap和iounmap的頭文件	writel等
/**********************************靜態映射虛擬地址的方法,用的是內核移植時的靜態映射表**********************************/
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>

#define 	GPJ0CON		S5PV210_GPJ0CON	//這個宏是在gpio-bank.h中定義的,是虛擬地址,這個宏還用到了regs-gpio.h中的宏,
										//以此類推。
#define 	GPJ0DAT		S5PV210_GPJ0DAT

static struct led_classdev myled1;
static struct led_classdev myled2;
static struct led_classdev myled3;


//這個函數綁定到了struct led_classdev這個結構體類型的myled變量中的brightness_set成員,當我們用戶寫這個led硬件的時候,因為是用led驅動框架
//寫的led驅動,所以我們是通過led驅動框架中的brightness這個文件,去寫或這個讀led硬件的,應為這個文件在驅動框架中具有了可讀可寫的屬性,是在
//struct //device_attribute結構中擁有的,並且最終綁定了到了這個設備類中。所以最後我們用戶去通過brightness這個屬性文件去寫led硬件的時候,會去執行led_br//ightness_store這個方法,這個方法中,又
//通過調用led_set_brightness這個函數,這個函數中通過led_cdev->brightness_set(led_cdev, value);這條語句,最終對應到了struct //led_classdev結構體中的brightness_set這個函數指針變量,而這個函數指針變量在我們填充那個結構體
//struct led_classdev中的brightness_set函數指針時,就已經綁定好了我們寫的那個寫操作硬件的函數了。所以這一條線就打通了.value就是用戶要寫的值
//value是枚舉,是enum led_brightness類型的枚舉,這個枚舉有三個值,分別是LED_OFF = 0  LED_XXX = 122  LED_FULL = 255,

static void whyx210_led1_set(struct led_classdev *led_cdev, enum led_brightness value)
{
	//printk(KERN_INFO "whyx210_led_set\n");
	
	writel(0x11111111, GPJ0CON);
	
	if (value == LED_OFF) {	//用戶輸入0時滅		對應用戶輸入的是echo 0 > brightness
	
		writel(readl(GPJ0DAT) | (1 << 3), GPJ0DAT);
		
	}else if (value == LED_FULL){	//用戶輸入255時亮		對應用戶輸入的是echo 255 > brightness
	
		
		writel(readl(GPJ0DAT) & ~(1 << 3), GPJ0DAT);	//這樣操作保持其他位值不變
		
	}
}

static void whyx210_led2_set(struct led_classdev *led_cdev, enum led_brightness value)
{
	//printk(KERN_INFO "whyx210_led_set\n");
	
	writel(0x11111111, GPJ0CON);
	
	if (value == LED_OFF) {	//用戶輸入0時滅		對應用戶輸入的是echo 0 > brightness
	
		writel(readl(GPJ0DAT) | (1 << 4), GPJ0DAT);
		
	}else if (value == LED_FULL){	//用戶輸入255時亮		對應用戶輸入的是echo 255 > brightness
	
		
		writel(readl(GPJ0DAT) & ~(1 << 4), GPJ0DAT);	//這樣操作保持其他位值不變
		
	}
}

static void whyx210_led3_set(struct led_classdev *led_cdev, enum led_brightness value)
{
	//printk(KERN_INFO "whyx210_led_set\n");
	
	writel(0x11111111, GPJ0CON);
	
	if (value == LED_OFF) {	//用戶輸入0時滅		對應用戶輸入的是echo 0 > brightness
	
		writel(readl(GPJ0DAT) | (1 << 5), GPJ0DAT);
		
	}else if (value == LED_FULL){	//用戶輸入255時亮		對應用戶輸入的是echo 255 > brightness
	
		
		writel(readl(GPJ0DAT) & ~(1 << 5), GPJ0DAT);	//這樣操作保持其他位值不變
		
	}
}


static int __init whyx210_led_init(void)
{
	int ret = -1;
	//led1
	//填充我們要註冊的struct led_classdev類型的結構體
	myled1.name = "led1";		//將來sys/class/leds/目錄下的那個led文件的名字。leds這個目錄在led-class.c中內核幫我們實現好了、
	myled1.brightness = 255;
	myled1.brightness_set = whyx210_led1_set;
	
	
	//去調用led驅動框架中為我們提供的led註冊函數led_classdev_register去註冊驅動
	//在led-class.c中int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
	ret = led_classdev_register(NULL, &myled1);
	
	if (ret < 0) {
		
		printk(KERN_ERR "led_classdev_register errro\n");
		return ret;
	}
	//printk(KERN_INFO "led_classdev_register success %s\n", myled.name);
/*********************************************************************************************/	
	//led2
	//填充我們要註冊的struct led_classdev類型的結構體
	myled2.name = "led2";		//將來sys/class/leds/目錄下的那個led文件的名字。leds這個目錄在led-class.c中內核幫我們實現好了、
	myled2.brightness = 255;
	myled2.brightness_set = whyx210_led2_set;
	
	
	//去調用led驅動框架中為我們提供的led註冊函數led_classdev_register去註冊驅動
	//在led-class.c中int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
	ret = led_classdev_register(NULL, &myled2);
	
	if (ret < 0) {
		
		printk(KERN_ERR "led_classdev_register errro\n");
		return ret;
	}
	//printk(KERN_INFO "led_classdev_register success %s\n", myled.name);
/*********************************************************************************************/		
	//led3
	//填充我們要註冊的struct led_classdev類型的結構體
	myled3.name = "led3";		//將來sys/class/leds/目錄下的那個led文件的名字。leds這個目錄在led-class.c中內核幫我們實現好了、
	myled3.brightness = 255;
	myled3.brightness_set = whyx210_led3_set;
	
	
	//去調用led驅動框架中為我們提供的led註冊函數led_classdev_register去註冊驅動
	//在led-class.c中int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
	ret = led_classdev_register(NULL, &myled3);
	
	if (ret < 0) {
		
		printk(KERN_ERR "led_classdev_register errro\n");
		return ret;
	}
	//printk(KERN_INFO "led_classdev_register success %s\n", myled.name);
	
	return 0;
}


static void __init whyx210_led_exit(void)
{
	led_classdev_unregister(&myled1);
	led_classdev_unregister(&myled2);
	led_classdev_unregister(&myled3);
	//printk(KERN_INFO "led_classdev_unregister success.\n");
}




module_init(whyx210_led_init);
module_exit(whyx210_led_exit);

MODULE_AUTHOR("why <[email protected]>");
MODULE_DESCRIPTION("whyx210 LED driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("whyx210_led");

現在先將led2和led3的代碼去掉,只留下led1,並且我們要知道我們怎麽去用platform平臺總線的方法去實現led的驅動,我們要有probe函數,和remove函數,分別對應的是驅動和設備匹配上後執行的probe函數,以及設備和驅動分離時的remove函數也就是卸載驅動的函數,我們要將數據部分的代碼寫入到platform的platform_data中,在驅動代碼的probe函數中,用參數的方式,將設備的數據部分得到,實現驅動和設備數據的代碼的分離邏輯框架。

上面的代碼只留下led1的驅動代碼後,代碼如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <asm/io.h>	//ioremap和iounmap的頭文件	writel等
/**********************************靜態映射虛擬地址的方法,用的是內核移植時的靜態映射表**********************************/
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>

#define 	GPJ0CON		S5PV210_GPJ0CON	//這個宏是在gpio-bank.h中定義的,是虛擬地址,這個宏還用到了regs-gpio.h中的宏,
										//以此類推。
#define 	GPJ0DAT		S5PV210_GPJ0DAT

static struct led_classdev myled1;


//這個函數綁定到了struct led_classdev這個結構體類型的myled變量中的brightness_set成員,當我們用戶寫這個led硬件的時候,因為是用led驅動框架
//寫的led驅動,所以我們是通過led驅動框架中的brightness這個文件,去寫或這個讀led硬件的,應為這個文件在驅動框架中具有了可讀可寫的屬性,是在
//struct //device_attribute結構中擁有的,並且最終綁定了到了這個設備類中。所以最後我們用戶去通過brightness這個屬性文件去寫led硬件的時候,會去執行led_br//ightness_store這個方法,這個方法中,又
//通過調用led_set_brightness這個函數,這個函數中通過led_cdev->brightness_set(led_cdev, value);這條語句,最終對應到了struct //led_classdev結構體中的brightness_set這個函數指針變量,而這個函數指針變量在我們填充那個結構體
//struct led_classdev中的brightness_set函數指針時,就已經綁定好了我們寫的那個寫操作硬件的函數了。所以這一條線就打通了.value就是用戶要寫的值
//value是枚舉,是enum led_brightness類型的枚舉,這個枚舉有三個值,分別是LED_OFF = 0  LED_XXX = 122  LED_FULL = 255,

static void whyx210_led1_set(struct led_classdev *led_cdev, enum led_brightness value)
{
	//printk(KERN_INFO "whyx210_led_set\n");
	
	writel(0x11111111, GPJ0CON);
	
	if (value == LED_OFF) {	//用戶輸入0時滅		對應用戶輸入的是echo 0 > brightness
	
		writel(readl(GPJ0DAT) | (1 << 3), GPJ0DAT);
		
	}else if (value == LED_FULL){	//用戶輸入255時亮		對應用戶輸入的是echo 255 > brightness
	
		
		writel(readl(GPJ0DAT) & ~(1 << 3), GPJ0DAT);	//這樣操作保持其他位值不變
		
	}
}

static int __init whyx210_led_init(void)
{
	int ret = -1;
	//led1
	//填充我們要註冊的struct led_classdev類型的結構體
	myled1.name = "led1";		//將來sys/class/leds/目錄下的那個led文件的名字。leds這個目錄在led-class.c中內核幫我們實現好了、
	myled1.brightness = 255;
	myled1.brightness_set = whyx210_led1_set;
	
	
	//去調用led驅動框架中為我們提供的led註冊函數led_classdev_register去註冊驅動
	//在led-class.c中int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
	ret = led_classdev_register(NULL, &myled1);
	
	if (ret < 0) {
		
		printk(KERN_ERR "led_classdev_register errro\n");
		return ret;
	}
	//printk(KERN_INFO "led_classdev_register success %s\n", myled.name);
	
	return 0;
}


static void __init whyx210_led_exit(void)
{
	led_classdev_unregister(&myled1);
}




module_init(whyx210_led_init);
module_exit(whyx210_led_exit);

MODULE_AUTHOR("why <[email protected]>");
MODULE_DESCRIPTION("whyx210 LED driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("whyx210_led");

makefile的代碼如下:

#ubuntu中的內核源碼樹目錄
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build

#我編譯的九鼎內核的源碼樹目錄
KERN_DIR = /root/xin_x210/kernel


obj-m	+= leds-why210.o

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	#arm-linux-gcc app.c -o app

cp:
	cp *.ko /root/rootfs/rootfs/driver_test/ -f
	#cp app /root/rootfs/rootfs/driver_test/ -f
.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
#	rm app -f 
	

makefile的代碼不用動

之後我們在led的驅動代碼中,修改並添加probe函數和led驅動需要的結構體。加入probe函數和remove函數,並且加入並填充led驅動需要的結構體,並將init函數也就是ismod加載驅動模塊時執行的函數,和exit函數也就是卸載驅動模塊時執行的函數,將這兩個函數中的內容分別改為用platform提供的註冊函數進行註冊驅動(led驅動的結構體)和用platform提供的註銷驅動(led驅動結構體)的方法。改動後的代碼如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <asm/io.h>	//ioremap和iounmap的頭文件	writel等
/**********************************靜態映射虛擬地址的方法,用的是內核移植時的靜態映射表**********************************/
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/platform_device.h>

#define 	GPJ0CON		S5PV210_GPJ0CON	//這個宏是在gpio-bank.h中定義的,是虛擬地址,這個宏還用到了regs-gpio.h中的宏,
										//以此類推。
#define 	GPJ0DAT		S5PV210_GPJ0DAT

static struct led_classdev myled1;


static void whyx210_led1_set(struct led_classdev *led_cdev, enum led_brightness value)
{
	//printk(KERN_INFO "whyx210_led_set\n");
	
	writel(0x11111111, GPJ0CON);
	
	if (value == LED_OFF) {	//用戶輸入0時滅		對應用戶輸入的是echo 0 > brightness
	
		writel(readl(GPJ0DAT) | (1 << 3), GPJ0DAT);
		
	}else if (value == LED_FULL){	//用戶輸入255時亮		對應用戶輸入的是echo 255 > brightness
	
		
		writel(readl(GPJ0DAT) & ~(1 << 3), GPJ0DAT);	//這樣操作保持其他位值不變
		
	}
}

static int why_led_remove(struct platform_device *dev)
{
	led_classdev_unregister(&myled1);
	
	return 0;
}

static int why_led_probe(struct platform_device *dev)
{
		int ret = -1;
	//led1
	//填充我們要註冊的struct led_classdev類型的結構體
	myled1.name = "led1";		//將來sys/class/leds/目錄下的那個led文件的名字。leds這個目錄在led-class.c中內核幫我們實現好了、
	myled1.brightness = 255;
	myled1.brightness_set = whyx210_led1_set;
	
	
	//去調用led驅動框架中為我們提供的led註冊函數led_classdev_register去註冊驅動
	//在led-class.c中int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
	ret = led_classdev_register(NULL, &myled1);
	
	if (ret < 0) {
		
		printk(KERN_ERR "led_classdev_register errro\n");
		return ret;
	}
	
	return 0;
}


static struct platform_driver why_led_driver = {
	.probe		= why_led_probe,
	.remove		= why_led_remove,
	.driver		= {
		.name		= "why_led",
		.owner		= THIS_MODULE,
	},
};

static int __init whyx210_led_init(void)
{
	return platform_driver_register(&why_led_driver);
}


static void __init whyx210_led_exit(void)
{
	platform_driver_unregister(&why_led_driver);
}




module_init(whyx210_led_init);
module_exit(whyx210_led_exit);

MODULE_AUTHOR("why <[email protected]>");
MODULE_DESCRIPTION("whyx210 LED driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("whyx210_led");

分析,此時probe和remove函數的內容還不是正確的,用makefile編譯能編譯通過,生成一個leds-why210.ko驅動模塊,我們將這個驅動模塊拷貝到根文件系統中,執行insmod的時候,對應的whyx210_led_init函數會被執行,會將led的驅動結構體數據結構註冊到內核中,此時我們在根文件系統的/sys/bus/platform/driver/目錄下面會看到有一個why_led的文件,說明我們的驅動註冊成功了,但是是用不了的,因為我們沒有提供platform總線下需要的platform_device,probe函數和remove也是不對的,如果正確的話,我們提供platform_device後,將其進行註冊後,當有led設備的時候,驅動也被加載的時候,那麽match函數會將led的驅動和設備進行匹配,驅動的probe函數會得以執行,led的數據部分是在platform_device中的,在驅動的probe函數中將platform_device中的數據部分得到,最後在probe函數中會進行一些註冊,這個時候,在根文件系統的/sys/bus/platform/driver/why_led目錄下就會有其他東西,這個時候我們的led驅動就是可以使用的了。

本文出自 “whylinux” 博客,謝絕轉載!

linux設備驅動模型之平臺總線實踐環節(一)