上一次做了一個kobject執行個體,這次做的是總線、裝置、驅動的執行個體。這兩個例子都比較虛,實際開發很難看見它們。對于一個急于開發某款硬體驅動的人來說意義不大,且對于有實際任務的你也很難靜下心來看這些蒼白的東西。
Ldd3的話:
許多驅動作者将不會需要這裡涉及的材料。這個水準的細節通常在總線級别處理,并且很少作者需要添加一個新總線類型。這個資訊是有用的,但是,對任何人好奇在 PCI, USB等層面的裡面發生了什麼或者誰需要在那個級别做改變。
我第一次看到這句話時就把裝置模型這章跳過了,現在做一些總線的驅動才會感到很多的疑惑,疑惑當然就是總線、裝置、驅動、sysfs之間的關系。雖然這些疑惑可能不會影響你開發驅動(很多人包括我認為驅動隻是對系統提供的接口的填充,最後主要是對硬體的了解),但請相信我,看完裝置模型後,你會感覺自己站在另一個高度去看待驅動。
開場白結束。
基本知識:略。
基本函數:網上有。
開發平台:還是那個平台。linux-3.2.36
執行個體結構圖:
曲線表示對應關系
有個總線叫mine,它有一個驅動叫my_dev,有兩個裝置叫my_dev0,my_dev1。my_dev0和my_dev1對應驅動my_dev。
先貼兩個.h檔案
//my_bus.h
#ifndef __MY_BUS_H__
#define __MY_BUS_H__
#define CHR_LEN 20
//模仿usb,usb用這個記錄一些特别的裝置
#define MY_DEVICE(vend, prod) \
.idVendor = (vend), \
.idProduct = (prod)
//是否用初始化
typedef enum
{
NO = 0,
YES = 1,
}initFg;//可以用個bool
struct my_device_id
{
__u16 idVendor;
__u16 idProduct;
initFg initFg;
};
#define MATCH_ID(id1, id2) ((id1->idVendor == id2->idVendor) & (id1->idProduct == id2->idProduct))
struct my_driver
{
char const *version;
struct module *module;
struct device_driver driver;
//在usb中沒用device_driver,而是自己定義了usb_driver和struct usb_device_driver等
struct driver_attribute *version_attr;
const struct my_device_id *id_table;
};
#define to_my_driver(drv) container_of(drv, struct my_driver, driver)
struct my_device
{
char const *name;
const struct my_device_id *id;
struct device_attribute *attr;
struct device dev;
struct my_driver *mydriver;
};
#define to_my_device(dev) container_of(dev, struct my_device, dev)
extern int register_my_device(struct my_device *);
extern void unregister_my_device(struct my_device *);
extern int register_my_driver(struct my_driver *);
extern void unregister_my_driver(struct my_driver *);
#endif//__MY_BUS_H__
//common.h
#ifndef __COMMON_H__
#define __COMMON_H__
#define DEBUG_EN
#define PRINT_COLOR_RESET "\33[0m"
#define PRINT_COLOR_BLACK "\33[30m"
#define PRINT_COLOR_RED "\33[31m"
#define PRINT_COLOR_GREEN "\33[32m"
#define PRINT_COLOR_YELLOW "\33[33m"
#define PRINT_COLOR_BLUE "\33[34m"
#define PRINT_COLOR_MAGENTA "\33[35m"
#define PRINT_COLOR_CYAN "\33[36m"
#define PRINT_COLOR_WHITE "\33[37m"
#define Printk(COLOR, ARGs...) \
printk(COLOR); \
printk(KERN_DEBUG ARGs); \
printk(PRINT_COLOR_RESET) \
#ifdef DEBUG_EN
#define MY_DEBUG(ARGs...) Printk(PRINT_COLOR_YELLOW, ARGs)
#else
#define MY_DEBUG(ARGs...) ;
#endif
#endif//__COMMON_H__
//my_bus.c
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include "my_bus.h"
#include "common.h"
MODULE_AUTHOR("wwxxxxll");
MODULE_LICENSE("Dual BSD/GPL");
struct my_bus_info
{
struct bus_type my_bus_type;
struct device my_bus;
struct bus_attribute *my_bus_attrs_version;
struct bus_attribute *my_bus_attrs_info;
char info[CHR_LEN];
};
#define to_my_bus(data) container_of(data, struct my_bus_info, my_bus_type)
static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
return snprintf(buf, CHR_LEN, "%s\n", "my_bus-1.0.0");
}
static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);//定義一個attr
static ssize_t show_bus_info(struct bus_type *bus, char *buf)
{
struct my_bus_info *my_bus_info_p = to_my_bus(bus);
return snprintf(buf, CHR_LEN, "%s\n", my_bus_info_p->info);
}
static ssize_t store_bus_info(struct bus_type *bus, const char *buf, size_t count)
{
struct my_bus_info *my_bus_info_p = to_my_bus(bus);
sscanf(buf, "%s", my_bus_info_p->info);
return count;
}
//S_IRUGO | S_IWUSR 允許root 來改變參數
static BUS_ATTR(info, S_IRUGO | S_IWUSR, show_bus_info, store_bus_info);
//熱插拔先不管
static int my_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
MY_DEBUG("my_bus_uevent\n");
return 0;
}
static int my_bus_match(struct device *dev, struct device_driver *driver)
{
//platform_data會在my_dvc.c中指派
struct my_device *mydev = (struct my_device*)dev->platform_data;
MY_DEBUG("my_bus_match\n");
if (!strncmp(mydev->name, driver->name, strlen(driver->name)))
{
mydev->mydriver = to_my_driver(driver);
return 1;
}
return 0;
}
static void my_bus_release(struct device *dev)
{
MY_DEBUG("my_bus_release\n");
}
static struct my_bus_info my_bus_info_data =
{
.my_bus_type =
{
.name = "mine",
.match = my_bus_match,
.uevent = my_bus_uevent,
},
.my_bus =
{
.init_name = "mine0",
.release = my_bus_release,
},
.my_bus_attrs_version = &bus_attr_version,
.my_bus_attrs_info = &bus_attr_info,
.info = "Hello World",
};
int register_my_device(struct my_device *mydev)
{
int ret = 0;
mydev->dev.bus = &my_bus_info_data.my_bus_type;
mydev->dev.parent = &my_bus_info_data.my_bus;
ret = device_register(&mydev->dev);
if (ret)
{
return ret;
}
if (mydev->attr == NULL)
{
return ret;
}
ret = device_create_file(&mydev->dev, mydev->attr);
if (ret)
{
device_unregister(&mydev->dev);
}
return ret;
}
EXPORT_SYMBOL(register_my_device);
void unregister_my_device(struct my_device *mydev)
{
device_unregister(&mydev->dev);
}
EXPORT_SYMBOL(unregister_my_device);
int register_my_driver(struct my_driver *driver)
{
int ret = 0;
driver->driver.bus = &my_bus_info_data.my_bus_type;
ret = driver_register(&driver->driver);
if (ret)
return ret;
if (driver->version_attr == NULL)
{
return ret;
}
ret = driver_create_file(&driver->driver, driver->version_attr);\
if (ret)
{
driver_unregister(&driver->driver);
}
return ret;
}
EXPORT_SYMBOL(register_my_driver);
void unregister_my_driver(struct my_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(unregister_my_driver);
static int __init my_bus_init(void)
{
int ret;
ret = bus_register(&my_bus_info_data.my_bus_type);
if (ret)
return ret;
ret = bus_create_file(&my_bus_info_data.my_bus_type, my_bus_info_data.my_bus_attrs_version);
if (ret)
goto fail;
ret = bus_create_file(&my_bus_info_data.my_bus_type, my_bus_info_data.my_bus_attrs_info);
if (ret)
goto fail1;
ret = device_register(&my_bus_info_data.my_bus);
if (ret)
{
goto fail2;
}
return 0;
fail2:
bus_remove_file(&my_bus_info_data.my_bus_type, my_bus_info_data.my_bus_attrs_info);
fail1:
bus_remove_file(&my_bus_info_data.my_bus_type, my_bus_info_data.my_bus_attrs_version);
fail:
bus_unregister(&my_bus_info_data.my_bus_type);
return ret;
}
static void __exit my_bus_exit(void)
{
device_unregister(&my_bus_info_data.my_bus);
bus_remove_file(&my_bus_info_data.my_bus_type, my_bus_info_data.my_bus_attrs_info);
bus_remove_file(&my_bus_info_data.my_bus_type, my_bus_info_data.my_bus_attrs_version);
bus_unregister(&my_bus_info_data.my_bus_type);
}
module_init(my_bus_init);
module_exit(my_bus_exit);
//調試bus
# insmod my_bus.ko
# ls /sys/bus/
mine platform
有我們的mine
你可以試試version 和 info,我就不做了
//my_dvr.c
#include <linux/module.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include "my_bus.h"
#include "common.h"
MODULE_AUTHOR("wwxxxxll");
MODULE_LICENSE("Dual BSD/GPL");
//假設一個裝置需要特殊的初始化
static const struct my_device_id my_device_list[] = {
{MY_DEVICE(0x0002, 0x0002), YES},
{}
};
static ssize_t show_version(struct device_driver *driver, char *buf)
{
struct my_driver *ldriver = to_my_driver(driver);
sprintf(buf, "%s\n", ldriver->version);
return strlen(buf);
}
static DRIVER_ATTR(version, S_IRUGO, show_version, NULL);//不說了
static bool special_match(const struct my_device_id *id, const struct my_device_id *sdvc_list)
{
int i = sizeof(my_device_list);
//雖然隻有一個,我們還是認為它有很多
while(i--)
{
if (MATCH_ID(sdvc_list, id))
{
return 1;
}
sdvc_list++;
}
return 0;
}
//特殊器件初始化
static void special_init(void)
{
MY_DEBUG("special_init\n");
}
//這裡我們做個probe的例子
static int my_dvr_probe(struct device *dev)
{
struct my_device *mydev = (struct my_device*)dev->platform_data;
MY_DEBUG("my_dvr_probe\n");//隻是列印太沒意思,是以做了一個特殊裝置判斷
if (special_match(mydev->id, mydev->mydriver->id_table))//mydev->mydriver的指派在bus的match中
{
special_init();
}
return 0;
}
static int my_dvr_remove(struct device *dev)
{
MY_DEBUG("my_dvr_remove\n");
return 0;
}
static struct my_driver my_dvr_driver = {
.version = "mydvr: 1.0.0",
.module = THIS_MODULE,
.driver = {
.name = "my_dev",
.probe = my_dvr_probe,
.remove = my_dvr_remove,
},
.version_attr = &driver_attr_version,
.id_table = my_device_list,
};
int my_dvr_init(void)
{
return register_my_driver(&my_dvr_driver);
}
void my_dvr_cleanup(void)
{
unregister_my_driver(&my_dvr_driver);
}
module_init(my_dvr_init);
module_exit(my_dvr_cleanup);
//調試my_dvr.c
# insmod my_dvr.ko
# ls /sys/bus/mine/drivers
my_dev
# ls /sys/bus/mine/drivers/my_dev/
bind uevent unbind version
有了my_dev驅動
//my_dvc.c
#include <linux/module.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include "my_bus.h"
#include "common.h"
MODULE_AUTHOR("wwxxxxll");
MODULE_LICENSE("Dual BSD/GPL");
//看到下面這個,是不是想起usb驅動
#define VENDOR_ID1 0x0001
#define PRODUCT_ID1 0x0001
#define VENDOR_ID2 0x0002
#define PRODUCT_ID2 0x0002
static const struct my_device_id my_dvc_table[] = {
{MY_DEVICE(VENDOR_ID1, PRODUCT_ID1), NO},
{MY_DEVICE(VENDOR_ID2, PRODUCT_ID2), NO},
{}
};
MODULE_DEVICE_TABLE(my_dvc, my_dvc_table);
static ssize_t my_dvc_show_info(struct device *devp, struct device_attribute *attr, char *buf)
{
return snprintf(buf, strlen("test device\n"), "test device\n");
}
//生成/sys/bus/mine/devices/my_dev0/info,老玩意了,不調試
static DEVICE_ATTR(info, S_IRUGO, my_dvc_show_info, NULL);
static void my_dev_release(struct device *dev)
{
struct my_device *mydev = (struct my_device*)dev->platform_data;
MY_DEBUG("%s_release\n", mydev->name);
}
static struct my_device my_dvcs[2] =
{
{
.name = "my_dev0",
.id = my_dvc_table,
.dev =
{
.platform_data = &my_dvcs[0],
.release = my_dev_release,
.init_name = "my_dev0",
},
.attr = &dev_attr_info,
},
{
.name = "my_dev1",
.id = &my_dvc_table[1],
.dev =
{
.platform_data = &my_dvcs[1],
.release = my_dev_release,
.init_name = "my_dev1",
},
},
};
int my_dvc_init(void)
{
int i = 0, ret = 0;
for (i = 0; i < 2; i++)
{
if ((ret = register_my_device(&my_dvcs[i])))
{
break;
}
}
if (ret)
{
while(--i)
{
unregister_my_device(&my_dvcs[i]);
}
}
return ret;
}
void my_dvc_cleanup(void)
{
int i=0;
for (i = 0; i < 2; i++) {
unregister_my_device(&my_dvcs[i]);
}
}
module_init(my_dvc_init);
module_exit(my_dvc_cleanup);
//調試my_dvc.c
# insmod my_dvc.ko
#dmesg
my_bus_uevent
my_bus_match
my_dvr_probe
my_bus_uevent
my_bus_match
my_dvr_probe
special_init
前三個是my_dev0
後四個是my_dev1,它是特殊裝置,是以調用special_init
檢視一下
# ls /sys/bus/mine/devices/
my_dev0 my_dev1
# ls /sys/bus/mine/devices/my_dev0
driver info power subsystem uevent
# ls /sys/bus/mine/devices/my_dev1
driver power subsystem uevent
看一下drivers
# ls -l /sys/bus/mine/drivers/my_dev/*
--w------- 1 0 0 4096 Jan 1 00:03 /sys/bus/mine/drivers/my_dev/bind
lrwxrwxrwx 1 0 0 0 Jan 1 00:03 /sys/bus/mine/drivers/my_dev/my_dev0 -> ../../../../devices/mine0/my_dev0
lrwxrwxrwx 1 0 0 0 Jan 1 00:03 /sys/bus/mine/drivers/my_dev/my_dev1 -> ../../../../devices/mine0/my_dev1
--w------- 1 0 0 4096 Jan 1 00:03 /sys/bus/mine/drivers/my_dev/uevent
--w------- 1 0 0 4096 Jan 1 00:03 /sys/bus/mine/drivers/my_dev/unbind
-r--r--r-- 1 0 0 4096 Jan 1 00:03 /sys/bus/mine/drivers/my_dev/version
看到連接配接了吧
# rmmod my_dvc
rmmod: remove 'my_bus': Resource temporarily unavailable
//my_bus沒解除安裝成功,應該是my_dvr用到了my_bus中的
extern int register_my_driver(struct my_driver *);
extern void unregister_my_driver(struct my_driver *);
是以會報Resource temporarily unavailable,先這樣
#dmesg
...
my_dvr_remove
my_bus_uevent
my_dev0_release
my_dvr_remove
my_bus_uevent
my_dev1_release
//同時解除安裝 my_dvr
//補一個解除安裝my_bus
#rmmod my_bus
#dmesg
...
my_bus_release
代碼下載下傳:
http://download.csdn.net/detail/xxxxxlllllxl/5736659
用的vs編輯,裡面有^M符号,不要管它。
class不說了。
下期預告:
熱插拔