至今, 我們已經看到大量低級框架和一個相對少的例子. 我們試圖在本章剩下部分中補充, 隨著我們進入 Linux 設備模型的更高級. 為此, 我們介紹一個新的虛擬總線, 我們稱為 lddbus, [46]并且修改 scullp 驅動來 "接入" 到這個總線.
再一次, 許多驅動作者將不會需要這里涉及的材料. 這個水平的細節(jié)通常在總線級別處理, 并且很少作者需要添加一個新總線類型. 這個信息是有用的, 但是, 對任何人好奇在 PCI, USB 等層面的里面發(fā)生了什么或者誰需要在那個級別做改變.
一個總線是處理器和一個或多個設備之間的通道. 為設備模型的目的, 所有的設備都通過一個總線連接, 甚至當它是一個內部的虛擬的,"平臺"總線. 總線可以插入另一個 - 一個 USB 控制器常常是一個 PCI 設備, 例如. 設備模型表示在總線和它們控制的設備之間的實際連接.
在 Linux 設備模型中, 一個總線由 bus_type 結構代表, 定義在 <linux/device.h>. 這個結構看來象:
struct bus_type {
char *name;
struct subsystem subsys;
struct kset drivers;
struct kset devices;
int (*match)(struct device *dev, struct device_driver *drv);
struct device *(*add)(struct device * parent, char * bus_id);
int (*hotplug) (struct device *dev, char **envp,
int num_envp, char *buffer, int buffer_size);
/* Some fields omitted */
};
name 成員是總線的名子, 有些同 pci. 你可從這個結構中見到每個總線是它自己的子系統(tǒng); 這個子系統(tǒng)不位于 sysfs 的頂層, 但是. 相反, 它們在總線子系統(tǒng)下面. 一個總線包含 2 個 ksets, 代表已知的總線的驅動和所有插入總線的設備. 所以, 有一套方法我們馬上將涉及.
如同我們提過的, 例子源碼包含一個虛擬總線實現(xiàn)稱為 lddbus. 這個總線建立它的 bus_type 結構, 如下:
struct bus_type ldd_bus_type = { .name = "ldd", .match = ldd_match, .hotplug = ldd_hotplug, };
注意很少 bus_type 成員要求初始化; 大部分由設備模型核心處理. 但是, 我們確實不得不指定總線的名子, 以及任何伴隨它的方法.
不可避免地, 一個新總線必須注冊到系統(tǒng), 通過一個對 bus_register 的調用. lddbus 代碼這樣做以這樣的方式:
ret = bus_register(&ldd_bus_type);
if (ret)
return ret;
這個調用可能失敗, 當然, 因此返回值必須一直檢查. 如果它成功, 新總線子系統(tǒng)已被添加到系統(tǒng); 在 sysfs 中 /sys/bus 的下面可以見到, 并且可能啟動添加設備.
如果有必要從系統(tǒng)中去除一個總線(當關聯(lián)模塊被去除, 例如), 調用調用 bus_unregister:
void bus_unregister(struct bus_type *bus);
有幾個給 bus_type 結構定義的方法; 它們允許總線代碼作為一個設備核心和單獨驅動之間的中介. 在 2.6.10 內核中定義的方法是:
int (match)(struct device device, struct device_driver *driver);
這個方法被調用, 大概多次, 無論何時一個新設備或者驅動被添加給這個總線. 它應當返回一個非零值如果給定的設備可被給定的驅動處理. (我們馬上進入設備和 device_driver 結構的細節(jié)). 這個函數(shù)必須在總線級別處理, 因為那是合適的邏輯存在的地方; 核心內核不能知道如何匹配每個可能總線類型的設備和驅動.
int (hotplug) (struct device device, char *envp, int num_envp, char buffer, int buffer_size);
這個模塊允許總線添加變量到環(huán)境中, 在產生一個熱插拔事件在用戶空間之前. 參數(shù)和 kset 熱插拔方法相同( 在前面的 "熱插拔事件產生" 一節(jié)中描述 ).
lddbus 驅動有一個非常簡單的匹配函數(shù), 它僅僅比較驅動和設備的名子:
static int ldd_match(struct device *dev, struct device_driver *driver)
{
return !strncmp(dev->bus_id, driver->name, strlen(driver->name));
}
當涉及到真實硬件, match 函數(shù)常常在有設備自身提供的硬件 ID 和驅動提供的 ID 之間, 做一些比較.
lddbus 熱插拔方法看來象這樣:
static int ldd_hotplug(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size)
{
envp[0] = buffer;
if (snprintf(buffer, buffer_size, "LDDBUS_VERSION=%s",
Version) >= buffer_size)
return -ENOMEM;
envp[1] = NULL;
return 0;
}
這里, 我們加入 lddbus 源碼的當前版本號, 只是以防有人好奇.
如果你在編寫總線級別的代碼, 你可能不得不對所有已經注冊到你的總線的設備或驅動進行一些操作. 它可能會誘惑人直接進入 bus_type 結構中的各種結構, 但是最好使用已經提供的幫助函數(shù).
為操作每個對總線已知的設備, 使用:
int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *));
這個函數(shù)列舉總線上的每個設備, 傳遞關聯(lián)的設備結構給 fn, 連同作為 data 來傳遞的值. 如果 start 是 NULL, 列舉從總線的第一個設備開始; 否則列舉從 start 之后的第一個設備開始. 如果 fn 返回一個非零值, 列舉停止并且那個值從 bus_for_each_dev 返回.
有一個類似的函數(shù)來列舉驅動:
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));
這個函數(shù)就像 buf_for_each_dev, 除了, 當然, 它替之作用于驅動.
應當注意, 這 2 個函數(shù)持有總線子系統(tǒng)的讀者/寫者旗標在工作期間. 因此試圖一起使用這 2 個會死鎖 -- 每個將試圖獲取同一個旗標. 修改總線的操作( 例如注銷設備 )也將鎖住. 因此, 小心使用 bus_for_each 函數(shù).
幾乎 Linux 驅動模型中的每一層都提供一個添加屬性的接口, 并且總線層不例外. bus_attribute 類型定義在 <linux/device.h> 如下:
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *bus, char *buf);
ssize_t (*store)(struct bus_type *bus, const char *buf,
size_t count);
};
我們已經見到 struct attribute 在 "缺省屬性" 一節(jié). bus_attribute 類型也包含 2 個方法來顯示和設置屬性值. 大部分在 kobject 之上的設備模型層以這種方式工作.
已經提供了一個方便的宏為在編譯時間創(chuàng)建和初始化 bus_attribute 結構:
BUS_ATTR(name, mode, show, store);
這個宏聲明一個結構, 產生它的名子通過前綴字符串 busattr 到給定的名子.
任何屬于一個總線的屬性應當明確使用 bus_create_file 來創(chuàng)建:
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);
屬性也可被去除, 使用:
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);
lddbus 驅動創(chuàng)建一個簡單屬性文件, 再次, 包含源碼版本號. show 方法和 bus_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);
創(chuàng)建屬性文件在模塊加載時間完成:
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
printk(KERN_NOTICE "Unable to create version attribute\n");
這個調用創(chuàng)建一個屬性文件(/sys/busldd/version) 包含 lddbus 代碼的版本號.
在最低層, Linux 系統(tǒng)中的每個設備由一個 struct device 代表:
struct device { struct device parent; struct kobject kobj; char bus_id[BUS_ID_SIZE]; struct bus_type bus; struct device_driver driver; void driver_data; void (release)(struct device dev); / Several fields omitted /};
有許多其他的 struct device 成員只對設備核心代碼感興趣. 但是, 這些成員值得了解:
struct device *parent
設備的 "parent" 設備 -- 它所附著到的設備. 在大部分情況, 一個父設備是某種總線或者主控制器. 如果 parent 是 NULL, 設備是一個頂層設備, 這常常不是你所要的.
struct kobject kobj;
代表這個設備并且連接它到層次中的 kobject. 注意, 作為一個通用的規(guī)則, device->kobj->parent 等同于 device->parent->kobj.
char bus_id[BUS_ID_SIZE];
唯一確定這個總線上的設備的字符串. PCI 設備, 例如, 使用標準的 PCI ID 格式, 包含域, 總線, 設備, 和功能號.
struct bus_type *bus;
確定設備位于哪種總線.
struct device_driver *driver;
管理這個設備的驅動; 我們查看 struct device_driver 在下一節(jié).
void *driver_data;
一個可能被設備驅動使用的私有數(shù)據(jù)成員.
void (release)(struct device dev);
當對這個設備的最后引用被去除時調用的方法; 它從被嵌入的 kobject 的 release 方法被調用. 注冊到核心的所有的設備結構必須有一個 release 方法, 否則內核打印出慌亂的抱怨.
最少, parent, bus_id, bus, 和 release 成員必須在設備結構被注冊前設置.
通常的注冊和注銷函數(shù)在:
int device_register(struct device *dev);
void device_unregister(struct device *dev);
我們已經見到 lddbus 代碼如何注冊它的總線類型. 但是, 一個實際的總線是一個設備并且必須單獨注冊. 為簡單起見, lddbus 模塊只支持一個單個虛擬總線, 因此這個驅動在編譯時建立它的設備:
static void ldd_bus_release(struct device *dev)
{
printk(KERN_DEBUG "lddbus release\n");
}
struct device ldd_bus = {
.bus_id = "ldd0",
.release = ldd_bus_release
};
這是頂級總線, 因此 parent 和 bus 成員留為 NULL. 我們有一個簡單的, no-op release 方法, 并且, 作為第一個(并且唯一)總線, 它的名子時 ldd0. 這個總線設備被注冊, 使用:
ret = device_register(&ldd_bus);
if (ret)
printk(KERN_NOTICE "Unable to register ldd0\n");
一旦調用完成, 新總線可在 sysfs 中 /sys/devices 下面見到. 任何加到這個總線的設備接著在 /sys/devices/ldd0 下顯示.
sysfs 中的設備入口可有屬性. 相關的結構是:
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, char *buf);
ssize_t (*store)(struct device *dev, const char *buf,
size_t count);
};
這些屬性結構可在編譯時建立, 使用這些宏:
DEVICE_ATTR(name, mode, show, store);
結果結構通過前綴 devattr 到給定名子上來命名. 屬性文件的實際管理使用通常的函數(shù)對來處理:
int device_create_file(struct device *device, struct device_attribute *entry);
void device_remove_file(struct device *dev, struct device_attribute *attr);
struct bus_type 的 dev_attrs 成員指向一個缺省的屬性列表, 這些屬性給添加到總線的每個設備創(chuàng)建.
設備結構包含設備模型核心需要的來模型化系統(tǒng)的信息. 大部分子系統(tǒng), 但是, 跟蹤關于它們駐留的設備的額外信息. 結果, 對設備很少由空設備結構所代表; 相反, 這個結構, 如同 kobject 結構, 常常是嵌入一個更高級的設備表示中. 如果你查看 struct pci_dev 的定義或者 struct usb_device 的定義, 你會發(fā)現(xiàn)一個 struct device 埋在其中. 常常地, 低層驅動甚至不知道 struct device, 但是有例外.
lddbus 驅動創(chuàng)建它自己的設備類型( struct ldd_device ) 并且期望單獨的設備驅動來注冊它們的設備使用這個類型. 它是一個簡單結構:
struct ldd_device {
char *name;
struct ldd_driver *driver;
struct device dev;
};
#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev);
這個結構允許驅動提供一個實際的名子給設備( 這可以清楚地不同于它的總線 ID, 存儲于設備結構) 以及一個這些驅動信息的指針. 給真實設備的結構常常還包含關于供應者信息, 設備型號, 設備配置, 使用的資源, 等等. 可以在 struct pci_dev (<linux/pci.h>) 或者 struct usb_device (<linux/usb.h>) 中找到好的例子. 一個方便的宏( to_ldd_device ) 也為 struct ldd_device 定義, 使得容易轉換指向被嵌入的結構的指針為 ldd_device 指針.
lddbus 輸出的注冊接口看來如此:
int register_ldd_device(struct ldd_device *ldddev)
{
ldddev->dev.bus = &ldd_bus_type;
ldddev->dev.parent = &ldd_bus;
ldddev->dev.release = ldd_dev_release;
strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE);
return device_register(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device);
這里, 我們簡單地填充一些嵌入的設備結構成員( 單個驅動不應當需要知道這個 ), 并且注冊這個設備到驅動核心. 如果我們想添加總線特定的屬性到設備, 我們可在這里做.
為顯示這個接口如何使用, 我們介紹另一個例子驅動, 我們稱為 sculld. 它是在第 8 章介紹的 scullp 驅動上的另一個變體. 它實現(xiàn)通用的內存區(qū)設備, 但是 sculld 也使用 Linux 設備模型, 通過 lddbus 接口.
sculld 驅動添加一個它自己的屬性到它的設備入口; 這個屬性, 稱為 dev, 僅僅包含關聯(lián)的設備號. 這個屬性可被一個模塊用來加載腳本或者熱插拔子系統(tǒng), 來自動創(chuàng)建設備節(jié)點, 當設備被添加到系統(tǒng)時. 這個屬性的設置遵循常用模式:
static ssize_t sculld_show_dev(struct device *ddev, char *buf)
{
struct sculld_dev *dev = ddev->driver_data;
return print_dev_t(buf, dev->cdev.dev);
}
static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL);
接著, 在初始化時間, 設備被注冊, 并且 dev 屬性被創(chuàng)建通過下面的函數(shù):
static void sculld_register_dev(struct sculld_dev *dev, int index)
{
sprintf(dev->devname, "sculld%d", index);
dev->ldev.name = dev->devname;
dev->ldev.driver = &sculld_driver;
dev->ldev.dev.driver_data = dev;
register_ldd_device(&dev->ldev);
device_create_file(&dev->ldev.dev, &dev_attr_dev);
}
注意, 我們使用 driver_data 成員來存儲指向我們自己的內部的設備結構的指針.
設備模型跟蹤所有對系統(tǒng)已知的驅動. 這個跟蹤的主要原因是使驅動核心能匹配驅動和新設備. 一旦驅動在系統(tǒng)中是已知的對象, 但是, 許多其他的事情變得有可能. 設備驅動可輸出和任何特定設備無關的信息和配置變量, 例如:
驅動由下列結構定義:
struct device_driver {
char *name;
struct bus_type *bus;
struct kobject kobj;
struct list_head devices;
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown) (struct device *dev);
};
再一次, 幾個結構成員被忽略( 全部內容見 <linux/device.h> ). 這里, name 是驅動的名子( 它在 sysfs 中出現(xiàn) ), bus 是這個驅動使用的總線類型, kobj 是必然的 kobject, devices 是當前綁定到這個驅動的所有設備的列表, probe 是一個函數(shù)被調用來查詢一個特定設備的存在(以及這個驅動是否可以使用它), remove 當設備從系統(tǒng)中去除時被調用, shutdown 在關閉時被調用來關閉設備.
使用 device_driver 結構的函數(shù)的形式, 現(xiàn)在應當看來是類似的(因此我們快速涵蓋它們). 注冊函數(shù)是:
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);
通常的屬性結構在:
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *drv, char *buf);
ssize_t (*store)(struct device_driver *drv, const char *buf,
size_t count);
};
DRIVER_ATTR(name, mode, show, store);
以及屬性文件以通常的方法創(chuàng)建:
int driver_create_file(struct device_driver *drv, struct driver_attribute *attr);
void driver_remove_file(struct device_driver *drv, struct driver_attribute *attr);
bus_type 結構含有一個成員( drv_attrs ) 指向一套缺省屬性, 對所有關聯(lián)到這個總線的驅動都創(chuàng)建.
如同大部分驅動核心結構的情形, device_driver 結構常常被發(fā)現(xiàn)嵌到一個更高級的, 總線特定的結構. lddbus 子系統(tǒng)不會和這樣的趨勢相反, 因此它已定義了它自己的 ldd_driver 結構:
struct ldd_driver {
char *version;
struct module *module;
struct device_driver driver;
struct driver_attribute version_attr;
};
#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver);
這里, 我們要求每個驅動提供特定當前軟件版本, 并且 lddbus 輸出這個版本字串為它知道的每個驅動. 總線特定的驅動注冊函數(shù)是:
int register_ldd_driver(struct ldd_driver *driver)
{
int ret;
driver->driver.bus = &ldd_bus_type;
ret = driver_register(&driver->driver);
if (ret)
return ret;
driver->version_attr.attr.name = "version";
driver->version_attr.attr.owner = driver->module;
driver->version_attr.attr.mode = S_IRUGO;
driver->version_attr.show = show_version;
driver->version_attr.store = NULL;
return driver_create_file(&driver->driver, &driver->version_attr);
}
這個函數(shù)的第一部分只注冊低級的 device_driver 結構到核心; 剩下的建立版本屬性. 因為這個屬性在運行時被創(chuàng)建, 我們不能使用 DRIVER_ATTR 宏; 反之, driver_attribute 結構必須手工填充. 注意我們設定屬性的擁有者為驅動模塊, 不是 lddbus 模塊; 這樣做的理由是可以在為這個屬性的 show 函數(shù)的實現(xiàn)中見到:
static ssize_t show_version(struct device_driver *driver, char *buf)
{
struct ldd_driver *ldriver = to_ldd_driver(driver);
sprintf(buf, "%s\n", ldriver->version);
return strlen(buf);
}
有人可能認為屬性擁有者應當是 lddbus 模塊, 因為實現(xiàn)這個屬性的函數(shù)在那里定義. 這個函數(shù), 但是, 是使用驅動自身所創(chuàng)建的 ldd_driver 結構. 如果那個結構在一個用戶空間進程試圖讀取版本號時要消失, 事情會變得麻煩. 指定驅動模塊作為屬性的擁有者阻止了模塊被卸載, 在用戶空間保持屬性文件打開時. 因為每個驅動模塊創(chuàng)建一個對 lddbus 模塊的引用, 我們能確信 lddbus 不會在一個不合適的時間被卸載.
為完整起見, sculld 創(chuàng)建它的 ldd_driver 結構如下:
static struct ldd_driver sculld_driver = { .version = "$Revision: 1.1 $", .module = THIS_MODULE, .driver = { .name = "sculld", }, };
一個簡單的對 register_ldd_driver 的調用添加它到系統(tǒng)中. 一旦完成初始化, 驅動信息可在 sysfs 中見到:
$ tree /sys/bus/ldd/drivers
/sys/bus/ldd/drivers
`-- sculld
|-- sculld0 -> ../../../../devices/ldd0/sculld0
|-- sculld1 -> ../../../../devices/ldd0/sculld1
|-- sculld2 -> ../../../../devices/ldd0/sculld2
|-- sculld3 -> ../../../../devices/ldd0/sculld3
`-- version
[46] 這個總線的邏輯名子, 當然, 應當是"sbus", 但是這個名子已經被一個真實的, 物理總線采用.
更多建議: