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