Linux下驅動:分層、分離機制學習筆記
一、分層、分離的概念
分層即把硬體相關和相對穩定的東西給抽出來,並向上提供統一的介面,每一層專注於自己的事情,比如輸入子系統。後續文章中會繼續介紹相關例子;
分離即把硬體相關和相對穩定的東西給分離開來,實際上即是bus-dev-drv模型(平臺匯流排、平臺裝置、平臺驅動),本文章中的led驅動即使用了此模型。
二、bus-dev-drv模型
在硬體相關部分(即硬體操作的部分,其實在drv中也會有操作硬體,只是基本上不用改動,比較穩定。要改硬體,一般改dev部分程式碼即可。),入口函式中會呼叫platform_device_register→platform_device_add→device_add,device_add函式的作用如下:
(1)把device(結構體)放入bus(結構體)的dev連結串列中去。即註冊。
(2)從bus(結構體)的drv(結構體)連結串列中取出每個drv,用bus的match函式判斷drv能否支援此dev。
(3)如果支援(實際上是判斷結構的.name成員是否一樣),呼叫drv(結構體)的probe函式。
在driver相關部分,會向上註冊一個結構體,實際上就是呼叫platform_driver_register函式→driver_register函式把driver結構體放到bus 裡(結構體)的driver連結串列裡:
(1)把driver(結構體)放入bus(結構體)的drv連結串列中去。 即註冊。
(2)從bus(結構體)的dev(結構體)連結串列中取出每個dev,跟此drv一一比較(用bus的match函式去比較)。
(3)如果支援,呼叫drv(結構體)的probe函式。總的來說,上述只不過是左右建立一種聯絡的機制,在probe裡面做什麼完全由自己決定,比如列印一句話,註冊一個字元裝置,或者註冊一個input_dev結構體。如果要修改硬體,只需要修改硬體相關的程式碼即可,右邊比較穩定的程式碼可以不動,這樣大家就有一種約定,其實不管是device還是driver(它們只是一個結構體而已)。
三、bus-dev-drv模型具體例項
以一個LED驅動的例子說明分層分離的概念,原始碼分為led_dev.c,led_drv.c,led_test.c。led_dev.c為硬體相關,led_drv.c為右邊較穩定的部分程式碼,led_test.c為應用程式測試程式碼。基本功能是點亮或關閉LED燈。
其實這個程式完全可以用簡單的字元型驅動程式框架實現,這裡只是為了理解平臺匯流排、裝置、驅動模型,而用此框架實現的驅動。具體程式碼如下:
led_dev.c :
</pre><pre name="code" class="cpp">/* 分配/設定/註冊一個platform_device */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
/* "~~~~~~~~~~~Tiny4412支援的Linux3.5中不需要用資源的方式~~~~~~~~~~~"
static struct resource led_resource[] = {
//暫存器起始地址,如果要換硬體暫存器,只需要在這裡修改即可
[0] = {
.start = 0x110002E0,
.end = 0x110002E0 + 8 - 1,//結束地址
.flags = IORESOURCE_MEM,//表示哪類資源
},
//哪根引腳,如果要換一個led亮,只需要修改下面的數字即可
[1] = {
.start = 0,//Tiny4412中GPM4(0-3)分別接led0-3,所以0、1、2、3分別表示led1、led2、led3、led4
.end = 0,
.flags = IORESOURCE_IRQ,
}
};*/
static void led_release(struct device * dev)
{
}
static struct platform_device led_dev = {
.name = "myled",//這個名字要和led_dev.c中的相同
.id = -1,
//.num_resources = ARRAY_SIZE(led_resource),
.num_resources = 0,
//.resource = led_resource,
.dev = {
.release = led_release, //如果不提供此函式,在解除安裝模組時會報錯
},
};
static int led_dev_init(void)
{
//platform_device_register→platform_device_add→device_add即把裝置放入平臺匯流排裡的裝置連結串列中去
//即載入lsmod led_dev.ko後,會呼叫driver的probe函式。
platform_device_register(&led_dev);
return 0;
}
static void led_dev_exit(void)
{
/*
rmmod led_dev模組後,呼叫此函式,而此函式會從bus匯流排的dev連結串列中找出此裝置,然後
去掉並根據match函式找到對應的drv,然後呼叫裡面(drv結構體)的remove函式做些
清理工作*/
platform_device_unregister(&led_dev);//會呼叫platform_driver中led_remove函式
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
led_drv.c :
/* 分配/設定/註冊一個platform_driver */
#include <linux/version.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#define DEVICE_NAME "bus_dev_drv_leds"
#define LED_NUM ARRAY_SIZE(led_gpios)
static int led_gpios[] = {
EXYNOS4X12_GPM4(0),
EXYNOS4X12_GPM4(1),
EXYNOS4X12_GPM4(2),
EXYNOS4X12_GPM4(3),
};
/*
static int major;
static struct class *cls;
static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
static int pin;
*/
static int led_open(struct inode *inode, struct file *file)
{
printk("Kernel:bus_dev_drv_leds OPEN\n");
/*
// 配置為輸出
*gpio_con &= ~(0x3<<(pin*2));
*gpio_con |= (0x1<<(pin*2));*/
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
printk("Kernel:dev_drv_leds WRITE\n");
if(copy_from_user(&val, buf, count)) // copy_to_user();
{
printk("LED_DRV:copy_from_user ERR!\n");
return -1;
}
if (val == 1)
{
// 點燈
//*gpio_dat &= ~(1<<pin);
gpio_set_value(led_gpios[0], 0);
}
else
{
// 滅燈
//*gpio_dat |= (1<<pin);
gpio_set_value(led_gpios[0], 1);
}
return 0;
}
/*
static struct file_operations led_fops = {
.owner = THIS_MODULE, // 這是一個巨集,推向編譯模組時自動建立的__this_module變數
.open = led_open,
.write = led_write,
};*/
static struct file_operations tiny4412_led_dev_fops = {
.owner = THIS_MODULE,
//.unlocked_ioctl = tiny4412_leds_ioctl,
.open = led_open,
.write = led_write,
};
static struct miscdevice tiny4412_led_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &tiny4412_led_dev_fops,
};
static int led_probe(struct platform_device *pdev)//這裡面想做什麼就做什麼,使用者決定
{
/*struct resource *res;
//根據platform_device的資源進行ioremap
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
gpio_con = ioremap(res->start, res->end - res->start + 1);
gpio_dat = gpio_con + 1;//指標加一相當於加4,即指向另外一個暫存器
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
pin = res->start;*/
int ret;
int i;
for (i = 0; i < LED_NUM; i++) {
ret = gpio_request(led_gpios[i], "LED");
if (ret) {
printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,
led_gpios[i], ret);
return ret;
}
s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
gpio_set_value(led_gpios[i], 1);
}
/* 註冊字元裝置驅動程式 */
printk("led_drv: led_probe, found led\n");
misc_register(&tiny4412_led_dev);
/*
major = register_chrdev(0, "myled", &led_fops);
cls = class_create(THIS_MODULE, "myled");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led"); //裝置檔案dev/led
*/
return 0;
}
static int led_remove(struct platform_device *pdev)
{
/* 解除安裝字元裝置驅動程式 */
int i;
printk("led_drv: led_remove, remove led\n");
for (i = 0; i < LED_NUM; i++) {
gpio_free(led_gpios[i]);
}
misc_deregister(&tiny4412_led_dev);
/*
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "myled");
iounmap(gpio_con);
*/
return 0;
}
struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",//這個名字要和led_dev.c中的相同
}
};
static int led_drv_init(void)
{
platform_driver_register(&led_drv);
return 0;
}
static void led_drv_exit(void)
{
platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
led_test.c : #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/* led_test on
* led_test off
*/
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/bus_dev_drv_leds", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
if (argc != 2)
{
printf("Usage :\n");
printf("%s <on|off>\n", argv[0]);
return 0;
}
if (strcmp(argv[1], "on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
return 0;
}
Makefile:
KERN_DIR = /home/samba/linuxKernel_ext4Fs_src/linux-3.5-2015-8
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += led_drv.o
obj-m += led_dev.o
如果是先裝載led_dev,會把dev加入bus的裝置連結串列中去,然後找對應的drv,此時找不到沒關係,等裝載了led_drv後,會把drv加入到bus的driver連結串列中去,然後會在dev連結串列中找一遍有沒有匹配的dev(這樣就相當於再找了一遍,只是找的是dev而已),如果找到,也將呼叫drv的probe函式。所以,不管是在dev連結串列中找裝置還是在drv連結串列中找驅動,只要找到了,都會呼叫drv中的probe函式,這樣裝置和驅動可以不同時載入,也可不分順序載入,通過試驗也表明,不管先裝載led_drv.ko還是先裝載led_dev.ko,都是等兩個都裝載了後才會匹配並呼叫drv的probe函式。
所以說,不管先載入led_dev.ko還是led_drv.ko,只要匹配了,就會呼叫drv結構的probe函式成員。不管先解除安裝led_dev還是led_drv,凡是第一個解除安裝後,就會呼叫drv結構的led_remove函式成員。