1. 程式人生 > >《5.linux驅動開發-第4部分-5.4.驅動框架入門之LED》

《5.linux驅動開發-第4部分-5.4.驅動框架入門之LED》

《5.linux驅動開發-第4部分-5.4.驅動框架入門之LED》

第一部分、章節目錄
5.4.1.何謂驅動框架
5.4.2.核心驅動框架中LED的基本情況
5.4.3.初步分析led驅動框架原始碼1
5.4.4.初步分析led驅動框架原始碼2
5.4.5.在核心中新增或去除某個驅動
5.4.6.基於驅動框架寫led驅動1
5.4.7.基於驅動框架寫led驅動2
5.4.8.基於驅動框架寫led驅動3
5.4.9.linux核心的gpiolib學習1
5.4.10.linux核心的gpiolib學習2
5.4.11.linux核心的gpiolib學習3
5.4.12.linux核心的gpiolib學習4
5.4.13.linux核心的gpiolib學習5
5.4.14.linux核心的gpiolib學習6
5.4.15.linux核心的gpiolib學習7
5.4.16.使用gpiolib完成led驅動
5.4.17.將驅動新增到核心中

第二部分、章節介紹
5.4.1.何謂驅動框架
本節主要解釋了什麼是驅動框架,為什麼需要驅動框架,基於驅動框架寫驅動有什麼優勢等問題。
5.4.2.核心驅動框架中LED的基本情況
本節綜合分析了2.6.35.7核心中LED驅動框架的整體設計,相關的檔案位置,九鼎移植時LED驅動的實現方法等。
5.4.3.初步分析led驅動框架原始碼1
本節對led框架的核心程式碼led-class.c檔案進行分析,主要分析了subsys_initcall巨集。
5.4.4.初步分析led驅動框架原始碼2
本節接上節繼續分析led-class.c檔案,主要分析了led_classdev_register函式。
5.4.5.在核心中新增或去除某個驅動
本節講述如何在核心原始碼目錄中新增或去除某個驅動,並且做實驗進行去除實驗和新增實驗,觀察sysfs中的變化。
5.4.6.基於驅動框架寫led驅動1
本節首先分析當前基於led驅動框架如何去編寫程式,把思路理順,然後動手編寫好驅動原始碼並編譯通過。
5.4.7.基於驅動框架寫led驅動2
本節對上節的程式碼進行實踐操作,並且在實踐中結合原始碼進行分析,通過這樣的對比分析讓大家更深入的理解驅動程式碼。
5.4.8.基於驅動框架寫led驅動3
本節在上節基礎上,講述如何將4個LED分開實現,這樣的實現更加靈活,也是真實驅動中採用的方式。
5.4.9.linux核心的gpiolib學習1
本節開始學習gpiolib,主要講了學習重點的三條主線,和本部分的學習方法,並且結合前面的課程找到了gpiolib建立的入口。
5.4.10.linux核心的gpiolib學習2
本節重點講了gpiolib中對gpio資訊的封裝結構體,以及核心中記錄各個GPIO的編號方法巨集定義等。
5.4.11.linux核心的gpiolib學習3
本節接上節繼續分析gpiolib,主要分析了記錄晶片中gpio bank的主結構體變數
5.4.12.linux核心的gpiolib學習4
本節接上節繼續分析gpiolib,本節到了三星驅動工程師寫的硬體操作函式部分,主要是設定GPIO為輸入模式、輸出模式的2個函式。
5.4.13.linux核心的gpiolib學習5
本節開始分析gpiolib的驅動框架部分,這部分是核心開發者編寫的。本節主要講述其中開放給goiolib使用者的介面
5.4.14.linux核心的gpiolib學習6
本節接上節繼續分析gpiolib的驅動框架部分,本節繼續分析其中開放給gpiolib使用者的介面。
5.4.15.linux核心的gpiolib學習7
本節接上節繼續分析gpiolib的驅動框架部分,主要分析gpiolib自己工作的部分,其中重點是各個attribute的實現程式碼。
5.4.16.使用gpiolib完成led驅動
本節使用gpiolib來操作硬體,繼續改造我們之前寫的led驅動,這個改造後得到的驅動就是一個真實的LED裝置驅動了。
5.4.17.將驅動新增到核心中
本節講述如何將一個寫好的驅動程式原始碼新增到核心中,並且可以在核心中去配置該驅動的編譯,這在實際工作中很有用。

第三部分、隨堂記錄
5.4.1.何謂驅動框架
5.4.1.1、驅動是誰寫的
(1)驅動開發工程師
(2)核心維護者
5.4.1.2、驅動程式設計協作要求
(1)介面標準化
(2)儘量降低驅動開發者難度
5.4.1.3、到底什麼是驅動框架
(1)核心中驅動部分維護者針對每個種類的驅動設計一套成熟的、標準的、典型的驅動實現,然後把不同廠家的同類硬體驅動中相同的部分抽出來自己實現好,再把不同部分留出介面給具體的驅動開發工程師來實現,這就叫驅動框架。
(2)核心維護者在核心中設計了一些統一管控系統資源的體系,這些體系讓核心能夠對資源在各個驅動之間的使用統一協調和分配,保證整個核心的穩定健康執行。譬如系統中所有的GPIO就屬於系統資源,每個驅動模組如果要使用某個GPIO就要先呼叫特殊的介面先申請,申請到後使用,使用完後要釋放。又譬如中斷號也是一種資源,驅動在使用前也必須去申請。這也是驅動框架的組成部分。
(3)一些特定的介面函式、一些特定的資料結構,這些是驅動框架的直接表現。

5.4.2.核心驅動框架中LED的基本情況
5.4.2.1、相關檔案
(1)drivers/leds目錄,這個目錄就是驅動框架規定的LED這種硬體的驅動應該待的地方。
(2)led-class.c和led-core.c,這兩個檔案加起來屬於LED驅動框架的第一部分,這兩個檔案是核心開發者提供的,他們描述的是核心中所有廠家的不同LED硬體的相同部分的邏輯。
(3)leds-xxxx.c,這個檔案是LED驅動框架的第2部分,是由不同廠商的驅動工程師編寫新增的,廠商驅動工程師結合自己公司的硬體的不同情況來對LED進行操作,使用第一部分提供的介面來和驅動框架進行互動,最終實現驅動的功能。
5.4.2.2、九鼎移植的核心中led驅動
(1)九鼎實際未使用核心推薦的led驅動框架
(2)drivers/char/led/x210-led.c
5.4.2.3、案例分析驅動框架的使用
(1)以leds-s3c24xx.c為例。leds-s3c24xx.c中通過呼叫led_classdev_register來完成我們的LED驅動的註冊,而led_classdev_register是在drivers/leds/led-class.c中定義的。所以其實SoC廠商的驅動工程師是呼叫核心開發者在驅動框架中提供的介面來實現自己的驅動的。
(2)驅動框架的關鍵點就是:分清楚核心開發者提供了什麼,驅動開發者自己要提供什麼
5.4.2.4、典型的驅動開發行業現狀
(1)核心開發者對驅動框架進行開發和維護、升級,對應led-class.c和led-core.c
(2)SoC廠商的驅動工程師對裝置驅動原始碼進行編寫、除錯,提供參考版本,對應leds-s3c24xx.c
(3)做產品的廠商的驅動工程師以SoC廠商提供的驅動原始碼為基礎,來做移植和除錯

5.4.3_4.初步分析led驅動框架原始碼1_2
5.4.3.1、涉及到的檔案
(1)led-core.c
(2)led-class.c
5.4.3.2、subsys_initcall
(1)經過基本分析,發現LED驅動框架中核心開發者實現的部分主要是led-class.c
(2)我們發現led-class.c就是一個核心模組,對led-class.c分析應該從下往上,遵從對模組的基本分析方法。
(3)為什麼LED驅動框架中核心開發者實現的部分要實現成一個模組?因為核心開發者希望這個驅動框架是可以被裝載/解除安裝的。這樣當我們核心使用者不需要這個驅動框架時可以完全去掉,需要時可以隨時加上。
(4)subsys_initcall是一個巨集,定義在linux/init.h中。經過對這個巨集進行展開,發現這個巨集的功能是:將其宣告的函式放到一個特定的段:.initcall4.init。
subsys_initcall
__define_initcall(“4”,fn,4)
(5)分析module_init巨集,可以看出它將函式放到了.initcall6.init段中。
module_init
__initcall
device_initcall
__define_initcall(“6”,fn,6)
(6)核心在啟動過程中需要順序的做很多事,核心如何實現按照先後順序去做很多初始化操作。核心的解決方案就是給核心啟動時要呼叫的所有函式歸類,然後每個類按照一定的次序去呼叫執行。這些分類名就叫.initcalln.init。n的值從1到8。核心開發者在編寫核心程式碼時只要將函式設定合適的級別,這些函式就會被連結的時候放入特定的段,核心啟動時再按照段順序去依次執行各個段即可。
(7)經過分析,可以看出,subsys_initcall和module_init的作用是一樣的,只不過前者所宣告的函式要比後者在核心啟動時的執行順序更早。

5.4.3.3、led_class_attrs
(1)什麼是attribute,對應將來/sys/class/leds/目錄裡的內容,一般是檔案和資料夾。這些檔案其實就是sysfs開放給應用層的一些操作介面(非常類似於/dev/目錄下的那些裝置檔案)
(2)attribute有什麼用,作用就是讓應用程式可以通過/sys/class/leds/目錄下面的屬性檔案來操作驅動進而操作硬體裝置。
(3)attribute其實是另一條驅動實現的路線。有區別於之前講的file_operations那條線。

5.4.3.4、led_classdev_register
led_classdev_register
device_create
(1)分析可知,led_classdev_register這個函式其實就是去建立一個屬於leds這個類的一個裝置。其實就是去註冊一個裝置。所以這個函式其實就是led驅動框架中核心開發者提供給SoC廠家驅動開發者的一個註冊驅動的介面。
(2)當我們使用led驅動框架去編寫驅動的時候,這個led_classdev_register函式的作用類似於我們之前使用file_operations方式去註冊字元裝置驅動時的register_chrdev函式。

5.4.5.在核心中新增或去除某個驅動
5.4.5.1、去除九鼎移植的LED驅動
(1)九鼎移植的驅動在應用層的介面在/sys/devices/platform/x210-led/目錄下,有led1、led2、led3、led4四個裝置檔案,各自管理一個led。
(2)要去掉九鼎自己移植的led驅動,要在make menucofig中去掉選擇項,然後重新make得到zImage,然後重啟時啟動這個新的zImage即可。
(3)新的核心啟動後,如果/sys/devices/platform/目錄下已經沒有了x210-led這個目錄,就說明我們去掉這個驅動成功了。
(4)為什麼make menuconfig就能去掉這個驅動?

5.4.5.2、新增led驅動框架支援
(1)當前核心中是沒有LED驅動框架的,我們要去新增它。

5.4.5.3、sysfs中的內容分析
5.4.5.4、後續展望:完成leds-x210.c

5.4.6.基於驅動框架寫led驅動1
5.4.6.1、分析
(1)參考哪裡? drivers/leds/leds-s3c24xx.c
(2)關鍵點:led_classdev_register
5.4.6.2、動手寫led驅動模組

#ubuntu的核心原始碼樹,如果要編譯在ubuntu中安裝的模組就開啟這2個
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build	

		
# 開發板的linux核心的原始碼樹目錄
KERN_DIR = /root/driver/kernel

obj-m	+= leds-s5pv210.o

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

cp:
	cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
	cp app /root/porting_x210/rootfs/rootfs/driver_test
	

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf app

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>



#define GPJ0CON		S5PV210_GPJ0CON
#define GPJ0DAT		S5PV210_GPJ0DAT

static struct led_classdev mydev;			// 定義結構體變數


// 這個函式就是要去完成具體的硬體讀寫任務的
static void s5pv210_led_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led_set\n");
	
	// 在這裡根據使用者設定的值來操作硬體
	// 使用者設定的值就是value
	if (value == LED_OFF)
	{
		// 使用者給了個0,希望LED滅
		writel(0x11111111, GPJ0CON);
		writel(((1<<3) | (1<<4) | (1<<5)), GPJ0DAT);
	}
	else
	{
		// 使用者給的是非0,希望LED亮
		writel(0x11111111, GPJ0CON);
		writel(((0<<3) | (0<<4) | (0<<5)), GPJ0DAT);
	}
}


static int __init s5pv210_led_init(void)
{
	// 使用者insmod安裝驅動模組時會呼叫該函式
	// 該函式的主要任務就是去使用led驅動框架提供的設備註冊函式來註冊一個裝置
	int ret = -1;
	
	mydev.name = "myled";
	mydev.brightness = 255;	
	mydev.brightness_set = s5pv210_led_set;
	
	ret = led_classdev_register(NULL, &mydev);
	if (ret < 0) {
		printk(KERN_ERR "led_classdev_register failed\n");
		return ret;
	}
	
	return 0;
}

static void __exit s5pv210_led_exit(void)
{
	led_classdev_unregister(&mydev);
}


module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

// MODULE_xxx這種巨集作用是用來新增模組描述資訊
MODULE_LICENSE("GPL");							// 描述模組的許可證
MODULE_AUTHOR("aston <[email protected]>");		// 描述模組的作者
MODULE_DESCRIPTION("s5pv210 led driver");		// 描述模組的介紹資訊
MODULE_ALIAS("s5pv210_led");					// 描述模組的別名資訊
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>


#define FILE	"/dev/test"			// 剛才mknod建立的裝置檔名

char buf[100];

int main(void)
{
	int fd = -1;
	int i = 0;
	
	fd = open(FILE, O_RDWR);
	if (fd < 0)
	{
		printf("open %s error.\n", FILE);
		return -1;
	}
	printf("open %s success..\n", FILE);

/*	
	// 讀寫檔案
	write(fd, "on", 2);
	sleep(2);
	write(fd, "off", 3);
	sleep(2);
	write(fd, "on", 2);
	sleep(2);
*/
/*
	write(fd, "1", 1);
	sleep(2);
	write(fd, "0", 1);
	sleep(2);
	write(fd, "1", 1);
	sleep(2);
*/
	while (1)
	{
		memset(buf, 0 , sizeof(buf));
		printf("請輸入 on | off \n");
		scanf("%s", buf);
		if (!strcmp(buf, "on"))
		{
			write(fd, "1", 1);
		}
		else if (!strcmp(buf, "off"))
		{
			write(fd, "0", 1);
		}
		else if (!strcmp(buf, "flash"))
		{
			for (i=0; i<3; i++)
			{
				write(fd, "1", 1);
				sleep(1);
				write(fd, "0", 1);
				sleep(1);
			}
		}	
		else if (!strcmp(buf, "quit"))
		{
			break;
		}
	}

	
	// 關閉檔案
	close(fd);
	
	return 0;
}

5.4.7.基於驅動框架寫led驅動2
5.4.7.1、程式碼實踐
(1)除錯
(2)分析
通過分析看出:
第1:我們寫的驅動確實工作了,被載入了,/sys/class/leds/目錄下確實多出來了一個表示裝置的資料夾。資料夾裡面有相應的操控led硬體的2個屬性brightness和max_brightness
第2:led-class.c中brightness方法有一個show方法和store方法,這兩個方法對應使用者在/sys/class/leds/myled/brightness目錄下直接去讀寫這個檔案時實際執行的程式碼。
當我們show brightness時,實際就會執行led_brightness_show函式
當我們echo 1 > brightness時,實際就會執行led_brightness_store函式
(3)show方法實際要做的就是讀取LED硬體資訊,然後把硬體資訊返回給我們即可。所以show方法和store方法必要要會去操控硬體。但是led-class.c檔案又屬於驅動框架中的檔案,它本身無法直接讀取具體硬體,因此在show和store方法中使用函式指標的方式呼叫了struct led_classdev結構體中的相應的讀取/寫入硬體資訊的方法。
(4)struct led_classdev結構體中的實際用來讀寫硬體資訊的函式,就是我們自己寫的驅動檔案leds-s5pv210.c中要提供的。

5.4.7.2、新增硬體操作

5.4.8.基於驅動框架寫led驅動3
5.4.8.1、在驅動中將4個LED分開
(1)好處。驅動層實現對各個LED裝置的獨立訪問,並嚮應用層展示出4個操作介面led1、led2、led3、led4,這樣應用層可以完全按照自己的需要對LED進行控制。
驅動的設計理念:不要對最終需求功能進行假定,而應該只是直接的對硬體的操作。有一個概念就是:機制和策略的問題。在硬體操作上驅動只應該提供機制而不是策略。策略由應用程式來做。
(2)如何實現
5.4.8.2、和leds-s3c24xx.c的不同
5.4.8.3、gpiolib引入
(1)一個事實:很多硬體都要用到GPIO、GPIO會複用
(2)如果同一個GPIO被2個驅動同時控制了,就會出現bug
(3)核心提供gpiolib來統一管理系統中所有GPIO
(4)gpiolib本身屬於驅動框架的一部分

#ubuntu的核心原始碼樹,如果要編譯在ubuntu中安裝的模組就開啟這2個
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build	

		
# 開發板的linux核心的原始碼樹目錄
KERN_DIR = /root/driver/kernel

obj-m	+= leds-s5pv210.o

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

cp:
	cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
	cp app /root/porting_x210/rootfs/rootfs/driver_test
	

.PHONY: clean	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf app

#include <linux/module.h>		// module_init  module_exit
#include <linux/init.h>			// __init   __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>



#define GPJ0CON		S5PV210_GPJ0CON
#define GPJ0DAT		S5PV210_GPJ0DAT

static struct led_classdev mydev1;			// 定義結構體變數
static struct led_classdev mydev2;			// 定義結構體變數
static struct led_classdev mydev3;			// 定義結構體變數

// 這個函式就是要去完成具體的硬體讀寫任務的
static void s5pv210_led1_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led1_set\n");
	
	writel(0x11111111, GPJ0CON);
	
	// 在這裡根據使用者設定的值來操作硬體
	// 使用者設定的值就是value
	if (value == LED_OFF)
	{
		// 使用者給了個0,希望LED滅
		//writel(0x11111111, GPJ0CON);
		// 讀改寫三部曲
		writel((readl(GPJ0DAT) | (1<<3)), GPJ0DAT);
	}
	else
	{
		// 使用者給的是非0,希望LED亮
		//writel(0x11111111, GPJ0CON);
		writel((readl(GPJ0DAT) & ~(1<<3)), GPJ0DAT);
	}
}

static void s5pv210_led2_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv2102_led_set\n");
	
	writel(0x11111111, GPJ0CON);
	
	// 在這裡根據使用者設定的值來操作硬體
	// 使用者設定的值就是value
	if (value == LED_OFF)
	{
		// 使用者給了個0,希望LED滅
		//writel(0x11111111, GPJ0CON);
		// 讀改寫三部曲
		writel((readl(GPJ0DAT) | (1<<4)), GPJ0DAT);
	}
	else
	{
		// 使用者給的是非0,希望LED亮
		//writel(0x11111111, GPJ0CON);
		writel((readl(GPJ0DAT) & ~(1<<4)), GPJ0DAT);
	}
}

static void s5pv210_led3_set(struct led_classdev *led_cdev,
			    enum led_brightness value)
{
	printk(KERN_INFO "s5pv210_led3_set\n");
	
	writel(0x11111111, GPJ0CON);
	
	// 在這裡根據使用者設定的值來操作硬體
	// 使用者設定的值就是value
	if (value == LED_OFF)
	{
		// 使用者給了個0,希望LED滅
		//writel(0x11111111, GPJ0CON);
		// 讀改寫三部曲
		writel((readl(GPJ0DAT) | (1<<5)), GPJ0DAT);
	}
	else
	{
		// 使用者給的是非0,希望LED亮
		//writel(0x11111111, GPJ0CON);
		writel((readl(GPJ0DAT) & ~(1<<5)), GPJ0DAT);
	}
}


static int __init s5pv210_led_init(void)
{
	// 使用者insmod安裝驅動模組時會呼叫該函式
	// 該函式的主要任務就是去使用led驅動框架提供的設備註冊函式來註冊一個裝置
	int ret = -1;
	
	// led1
	mydev1.name = "led1";
	mydev1.brightness = 255;	
	mydev1.brightness_set = s5pv210_led1_set;
	
	ret = led_classdev_register(NULL, &mydev1);
	if (ret < 0) {
		printk(KERN_ERR "led_classdev_register failed\n");
		return ret;
	}
	
	// led2
	mydev2.name = "led2";
	mydev2.brightness = 255;	
	mydev2.brightness_set = s5pv210_led2_set;
	
	ret = led_classdev_register(NULL, &mydev2);
	if (ret < 0) {
		printk(KERN_ERR "led_classdev_register failed\n");
		return ret;
	}
	
	// led3
	mydev3.name = "led3";
	mydev3.brightness = 255;	
	mydev3.brightness_set = s5pv210_led3_set;
	
	ret = led_classdev_register(NULL, &mydev3);
	if (ret < 0) {
		printk(KERN_ERR "led_classdev_register failed\n");
		return ret;
	}
	
	return 0;
}

static void __exit s5pv210_led_exit(void)
{
	led_classdev_unregister(&mydev1);
	led_classdev_unregister(&mydev2);
	led_classdev_unregister(&mydev3);
}


module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);

// MODULE_xxx這種巨集作用是用來新增模組描述資訊
MODULE_LICENSE("GPL");							// 描述模組的許可證
MODULE_AUTHOR("aston <[email protected]>");		// 描述模組的作者
MODULE_DESCRIPTION("s5pv210 led driver");		// 描述模組的介紹資訊
MODULE_ALIAS("s5pv210_led");					// 描述模組的別名資訊
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>


#define FILE	"/dev/test"			// 剛才mknod建立的裝置檔名

char buf[100];

int main(void)
{
	int fd = -1;
	int i = 0;
	
	fd = open(FILE, O_RDWR);
	if (fd < 0)
	{
		printf("open %s error.\n", FILE);
		return -1;
	}
	printf("open %s success..\n", FILE);

/*	
	// 讀寫檔案
	write(fd, "on", 2);
	sleep(2);
	write(fd, "off", 3);
	sleep(2);
	write(fd, "on", 2);
	sleep(2);
*/
/*
	write(fd, "1", 1);
	sleep(2);
	write(fd, "0", 1);
	sleep(2);
	write(fd, "1", 1);
	sleep(2);
*/
	while (1)
	{
		memset(buf, 0 , sizeof(buf));
		printf("請輸入 on | off \n");
		scanf("%s", buf);
		if (!strcmp(buf, "on"))
		{
			write(fd, "1", 1);
		}
		else if (!strcmp(buf, "off"))
		{
			write(fd, "0", 1);
		}
		else if (!strcmp(buf, "flash"))
		{
			for (i=0; i<3; i++)
			{
				write(fd, "1", 1);
				sleep(1);
				write(fd, "0", 1);
				sleep(1);
			}
		}	
		else if (!strcmp(buf,