前面九章分別對linux驅動模型中的細節部分進行了分析,本節作爲小節,使用一個簡單的例子,分別使用前面分析的內容,實現一個簡單的總線,設備,驅動之間的關係。
實現一條總線
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *Version = "$Revision: 1.9 $";
/* 驅動和設備匹配 */
static int my_match(struct device *dev, struct device_driver *driver)
{
return !strncmp(dev_name(dev), driver->name, strlen(driver->name));
}
static void my_bus_release(struct device *dev)
{
printk(KERN_DEBUG "my bus release\n");
}
/* 總線也是一個設備 */
struct device my_bus = {
.init_name = "my_bus0",
.release = my_bus_release
};
/* 定義一個總線 */
struct bus_type my_bus_type = {
.name = "my_bus",
.match = my_match,
};
/* 導出總線和總線設備 */
EXPORT_SYMBOL(my_bus);
EXPORT_SYMBOL(my_bus_type);
/*
* Export a simple attribute.
*/
static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", Version);
}
static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);
static int __init my_bus_init(void)
{
int ret;
/*註冊總線*/
ret = bus_register(&my_bus_type);
if (ret)
return ret;
/*創建屬性文件*/
if (bus_create_file(&my_bus_type, &bus_attr_version))
printk(KERN_NOTICE "Fail to create version attribute!\n");
/*註冊總線設備*/
ret = device_register(&my_bus);
if (ret)
printk(KERN_NOTICE "Fail to register device:my_bus!\n");
return ret;
}
static void my_bus_exit(void)
{
device_unregister(&my_bus);
bus_unregister(&my_bus_type);
}
module_init(my_bus_init);
module_exit(my_bus_exit);
這裏是註冊一個總線,和前面總線章節那個例子相比,總線作爲一個設備註冊了下來(我們可以sys/devices/目錄下面看到)。
當然也同樣實現了一個屬性文件,可以使用它的讀功能,查看版本。
這裏看一下注冊總線前後的對比:
可以看到總線名和設備名都能對應上。
同時因爲這對總線的設備註冊時沒父節點以及掛接到別的bus上的,所以這個設備直接是在/sys/devices/這個目錄下。
同時我們這裏對總線和總線所代表的設備的符號進行了導出,可以讓其他內核模塊使用。
實現一個掛接在上面總線的設備
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
MODULE_LICENSE("Dual BSD/GPL");
/* 使用bus模塊中導出的符號 */
extern struct device my_bus;
extern struct bus_type my_bus_type;
/* Why need this ?*/
static void my_dev_release(struct device *dev)
{
}
struct device my_dev = {
.bus = &my_bus_type,
.parent = &my_bus,
.release = my_dev_release,
};
/*
* Export a simple attribute.
*/
static ssize_t mydev_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%s\n", "This is my device!");
}
static DEVICE_ATTR(dev, S_IRUGO, mydev_show, NULL);
static int __init my_device_init(void)
{
int ret = 0;
/* 初始化設備 */
dev_set_name(&my_dev, "my_dev");
/*註冊設備*/
ret = device_register(&my_dev);
/*創建屬性文件*/
device_create_file(&my_dev, &dev_attr_dev);
return ret;
}
static void my_device_exit(void)
{
device_unregister(&my_dev);
}
module_init(my_device_init);
module_exit(my_device_exit);
這個設備的實現就簡單很多了,初始化時,確定了設備是掛載那個總線下面,其父設備是那個後,註冊這個設備即可。
這裏也實現了一個讀的屬性文件。
設備名叫“my_dev”
實現一個掛接在上面總線的驅動程序
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
MODULE_LICENSE("Dual BSD/GPL");
/* 使用導出的總線 */
extern struct bus_type my_bus_type;
static int my_probe(struct device *dev)
{
printk("Driver found device which my driver can handle!\n");
return 0;
}
static int my_remove(struct device *dev)
{
printk("Driver found device unpluged!\n");
return 0;
}
struct device_driver my_driver = {
.name = "my_dev", /* 注意這裏和設備名字一樣 */
.bus = &my_bus_type, /* 掛接我們實現的總線 */
.probe = my_probe,
.remove = my_remove,
};
/*
* Export a simple attribute.
*/
static ssize_t mydriver_show(struct device_driver *driver, char *buf)
{
return sprintf(buf, "%s\n", "This is my driver!");
}
static DRIVER_ATTR(drv, S_IRUGO, mydriver_show, NULL);
static int __init my_driver_init(void)
{
int ret = 0;
/*註冊驅動*/
ret = driver_register(&my_driver);
/*創建屬性文件*/
ret = driver_create_file(&my_driver, &driver_attr_drv);
return ret;
}
static void my_driver_exit(void)
{
driver_unregister(&my_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
驅動這裏要注意兩點:
1.同樣綁定了我們實現的總線
2.驅動的name和設備那邊的name一樣
同時我們也知道,probe函數是總線和驅動匹配上後,被調用的。
實驗測試:
顯示看一下總線以及總線設備安裝前後的對比。
當然這時候總線下面的設備和驅動都是沒東西的。
在總線安裝的前提下,安裝設備
這裏要知道的一點是,bus下的設備都是,devices下的符號鏈接,而具體設備裏的subsystme則是bus下的具體總線的符號鏈接。
有了上面兩個,就可以在devices目錄下直接進入bus目錄,bus也可以直接進入devices目錄。
在總線安裝的前提下,安裝驅動
因爲上面我們先安裝了設備,這裏再安裝驅動。所以驅動安裝完就直接匹配上了設備。
下面我們先卸載設備。
可以看到,在卸載了設備後,因爲沒了和驅動匹配的設備,所以驅動下面的設備的符號鏈接也被移除了。
如果我們分析一下linux中的那條虛擬總線platform總線,會發現,其實和我們上面的實現是一樣的。
只不過platform總選,完善了match的id_table的情況。
platform總線,實現了一個uevnt函數,來通知上層。
總之,基本的驅動模型以及實現。