1 はじめに
Linux ドライバー開発では、デバイスとデバイス ドライバーが基本概念です。カーネルの中心的な考え方は、デバイスとそのドライバーに対してそれぞれ device と device_driver という 2 つのデータ構造を定義することです。この記事では、これら 2 つのデータ構造に焦点を当てて、次のような Linux デバイス モデルのコア ロジックを紹介します。
2. struct device と struct device_driver
Linux カーネルのソース コードを読むと、特定のモジュールのロジックの 60% 以上が、コア データ構造、特にデバイス モデル部分を通じて理解できます。include/linux/device.h では、Linux カーネルはデバイス モデルの 2 つの最も重要なデータ構造、struct device と struct device_driver を定義します。
#「」 デバイスの構造は非常に複雑です (ただし、Linux カーネル開発者の質は非常に高く、このインターフェイスに関するコメントは非常に詳細です。興味のある学生はカーネルのソース コードを参照できます)。ここでは、非常に複雑なものをいくつか選択します。デバイス モデルを理解するために重要であり、主要なフィールドについて説明します。
parent、デバイスの親デバイス。通常は、デバイスが従属するバス、コントローラ、およびその他のデバイスです。
p、構造体デバイスのプライベート データ構造ポインター。このポインターは、サブデバイスのリンク リスト、バス/ドライバー/プレゼントなどのデバイスに追加するために使用されるリンク リスト ヘッダーを保存します。ソースを表示できます。詳細についてはコードを参照してください。
kobj、このデータ構造に対応する struct kobject。
init_name、デバイスの名前。
注 1: デバイス モデルでは、名前は非常に重要な変数です。カーネルに登録されているデバイスには有効な名前が必要です。この名前は、初期化中に、または「バス名デバイス ID」に従ってカーネルによって指定できます。 。
type、struct device_type 構造体は、新しいバージョンのカーネルで新たに導入された構造体であり、struct device との関係は、後で詳しく説明する struct kobj_type と struct kobject の関係と非常によく似ています。
bus、デバイスが属するバス (詳細は後述)。
driver、デバイスに対応するデバイスドライバー。
platform_data、特定のプラットフォーム関連データを保存するために使用されるポインター。特定のドライバー モジュールはここにプライベート データを一時的に保存し、必要に応じて取り出すことができるため、デバイス モデルはポインターの実際の意味を気にしません。
power、pm_domain、電源管理関連のロジックについては、電源管理のトピックで後ほど説明します。
ピン、「PINCTRL」機能、まだ説明されていません。
uma_node、「NUMA」関数、まだ説明されていません。
dma_mask~archdata、DMA 関連の関数についてはまだ説明しません。
devt, dev_t は 32 ビット整数で、2 つの部分 (メジャーとマイナー) で構成されます。デバイス ノード (キャラクター デバイスとブロック デバイス) の形式でユーザー空間へのインターフェイスを提供する必要があるデバイスでは、 used as デバイス番号が使用されます。ここで、この変数は主に、次のように、sys ファイル システム内のデバイス番号を持つデバイスごとに、/sys/dev/* の下に対応するディレクトリを作成するために使用されます。
1|root@android:/storage/sdcard0 #ls /sys/dev/char/1:1:1/ 1:11/ 1:13/ 1:14/ 1:2/ 1:3/ 1:5/ 1:7/ 1:8/ 1:9/
1|root@android:/storage/sdcard0 #ls /sys/dev/char/1:11:1/ 1:11/ 1:13/ 1:14/
groups、デバイスのデフォルトの属性セット。デバイスが登録されると、対応するファイルが sysfs に自動的に作成されます。
1|root@android:/storage/sdcard0 # ls /sys/dev/char/1:1
/sys/dev/char/1:1
class、デバイスが属するクラス。
」
1: /* include/linux/device.h, line 213 */ 2: struct device_driver { 3: const char *name; 4: struct bus_type *bus; 5: 6: struct module *owner; 7: const char *mod_name; /* used for built-in modules */ 8: 9: bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ 10: 11: const struct of_device_id *of_match_table; 12: const struct acpi_device_id *acpi_match_table; 13: 14: int (*probe) (struct device *dev); 15: int (*remove) (struct device *dev); 16: void (*shutdown) (struct device *dev); 17: int (*suspend) (struct device *dev, pm_message_t state); 18: int (*resume) (struct device *dev); 19: const struct attribute_group **groups; 20: 21: const struct dev_pm_ops *pm; 22: 23: struct driver_private *p; 24: };
“
device_driver就简单多了(在早期的内核版本中driver的数据结构为”struct driver”,不知道从哪个版本开始,就改成device_driver了):
name,该driver的名称。和device结构一样,该名称非常重要,后面会再详细说明。
bus,该driver所驱动设备的总线设备。为什么driver需要记录总线设备的指针呢?因为内核要保证在driver运行前,设备所依赖的总线能够正确初始化。
owner、mod_name,內核module相关的变量,暂不描述。
suppress_bind_attrs,是不在sysfs中启用bind和unbind attribute,如下:root@android:/storage/sdcard0 # ls /sys/bus/platform/drivers/switch-gpio/
bind uevent unbind
在kernel中,bind/unbind是从用户空间手动的为driver绑定/解绑定指定的设备的机制。这种机制是在bus.c中完成的,后面会详细解释。probe、remove,这两个接口函数用于实现driver逻辑的开始和结束。Driver是一段软件code,因此会有开始和结束两个代码逻辑,就像PC程序,会有一个main函数,main函数的开始就是开始,return的地方就是结束。而内核driver却有其特殊性:在设备模型的结构下,只有driver和device同时存在时,才需要开始执行driver的代码逻辑。这也是probe和remove两个接口名称的由来:检测到了设备和移除了设备(就是为热拔插起的!)。
shutdown、suspend、resume、pm,电源管理相关的内容,会在电源管理专题中详细说明。
groups,和struct device结构中的同名变量类似,driver也可以定义一些默认attribute,这样在将driver注册到内核中时,内核设备模型部分的代码(driver/base/driver.c)会自动将这些attribute添加到sysfs中。
p,私有数据的指针,具体的driver代码可以把任何需要的内容放在这里,反正设备模型代码不关心。
”
3. 设备模型框架下驱动开发的基本步骤
在设备模型框架下,设备驱动的开发是一件很简单的事情,主要包括2个步骤:
步骤1:分配一个struct device类型的变量,填充必要的信息后,把它注册到内核中。
步骤2:分配一个struct device_driver类型的变量,填充必要的信息后,把它注册到内核中。
这两步完成后,内核会在合适的时机(后面会讲),调用struct device_driver变量中的probe、remove、suspend、resume等回调函数,从而触发或者终结设备驱动的执行。而所有的驱动程序逻辑,都会由这些回调函数实现,此时,驱动开发者眼中便不再有“设备模型”,转而只关心驱动本身的实现。
“
以上两个步骤的补充说明:
\1. 一般情况下,Linux驱动开发很少直接使用device和device_driver,因为内核在它们之上又封装了一层,如soc device、platform device等等,而这些层次提供的接口更为简单、易用(也正是因为这个原因,本文并不会过多涉及device、device_driver等模块的实现细节)。
\2. 内核提供很多struct device结构的操作接口(具体可以参考include/linux/device.h和drivers/base/core.c的代码),主要包括初始化(device_initialize)、注册到内核(device_register)、分配存储空间+初始化+注册到内核(device_create)等等,可以根据需要使用。
\3. device和device_driver必须具备相同的名称,内核才能完成匹配操作,进而调用device_driver中的相应接口。这里的同名,作用范围是同一个bus下的所有device和device_driver。
\4. device和device_driver必须挂载在一个bus之下,该bus可以是实际存在的,也可以是虚拟的。
\5. driver开发者可以在struct device变量中,保存描述设备特征的信息,如寻址空间、依赖的GPIOs等,因为device指针会在执行probe等接口时传入,这时driver就可以根据这些信息,执行相应的逻辑操作了。
”
4. 设备驱动probe的时机
所谓的”probe”,是指在Linux内核中,如果存在相同名称的device和device_driver(注:还存在其它方式,我们先不关注了),内核就会执行device_driver中的probe回调函数,而该函数就是所有driver的入口,可以执行诸如硬件设备初始化、字符设备注册、设备文件操作ops注册等动作(”remove”是它的反操作,发生在device或者device_driver任何一方从内核注销时,其原理类似,就不再单独说明了)。
设备驱动prove的时机有如下几种(分为自动触发和手动触发):
“
注2:probe动作实际是由bus模块(会在下一篇文章讲解)实现的,这不难理解:device和device_driver都是挂载在bus这根线上,因此只有bus最清楚应该为哪些device、哪些driver配对。
注3:每个bus都有一个drivers_autoprobe变量,用于控制是否在device或者driver注册时,自动probe。该变量默认为1(即自动probe),bus模块将它开放到sysfs中了,因而可在用户空间修改,进而控制probe行为。
”
5. 其它杂项
5.1 device_attribute和driver_attribute
在”Linux设备模型(4)_sysfs”中,我们有讲到,大多数时候,attribute文件的读写数据流为:vfs—->sysfs—->kobject—->attibute—->kobj_type—->sysfs_ops—->xxx_attribute,其中kobj_type、sysfs_ops和xxx_attribute都是由包含kobject的上层数据结构实现。
Linux内核中关于该内容的例证到处都是,device也不无例外的提供了这种例子,如下:
1: /* driver/base/core.c, line 118 */ 2: static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr, 3: char *buf) 4: { 5: struct device_attribute *dev_attr = to_dev_attr(attr); 6: struct device *dev = kobj_to_dev(kobj); 7: ssize_t ret = -EIO; 8: 9: if (dev_attr->show) 10: ret = dev_attr->show(dev, dev_attr, buf); 11: if (ret >= (ssize_t)PAGE_SIZE) { 12: print_symbol("dev_attr_show: %s returned bad count\n", 13: (unsigned long)dev_attr->show); 14: } 15: return ret; 16: } 17: 18: static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr, 19: const char *buf, size_t count) 20: { 21: struct device_attribute *dev_attr = to_dev_attr(attr); 22: struct device *dev = kobj_to_dev(kobj); 23: ssize_t ret = -EIO; 24: 25: if (dev_attr->store) 26: ret = dev_attr->store(dev, dev_attr, buf, count); 27: return ret; 28: } 29: 30: static const struct sysfs_ops dev_sysfs_ops = { 31: .show = dev_attr_show, 32: .store = dev_attr_store, 33: }; 34: 35: /* driver/base/core.c, line 243 */ 36: static struct kobj_type device_ktype = { 37: .release = device_release, 38: .sysfs_ops = &dev_sysfs_ops, 39: .namespace = device_namespace, 40: }; 41: 42: /* include/linux/device.h, line 478 */ 43: /* interface for exporting device attributes */ 44: struct device_attribute { 45: struct attribute attr; 46: ssize_t (*show)(struct device *dev, struct device_attribute *attr, 47: char *buf); 48: ssize_t (*store)(struct device *dev, struct device_attribute *attr, 49: const char *buf, size_t count); 50: };
至于driver的attribute,则要简单的多,其数据流为:vfs—->sysfs—->kobject—->attribute—->driver_attribute,如下:
1: /* include/linux/device.h, line 247 */ 2: /* sysfs interface for exporting driver attributes */ 3: 4: struct driver_attribute { 5: struct attribute attr; 6: ssize_t (*show)(struct device_driver *driver, char *buf); 7: ssize_t (*store)(struct device_driver *driver, const char *buf, 8: size_t count); 9: }; 10: 11: #define DRIVER_ATTR(_name, _mode, _show, _store) \ 12: struct driver_attribute driver_attr_##_name = \ 13: __ATTR(_name, _mode, _show, _store)
5.2 device_type
device_type是内嵌在struct device结构中的一个数据结构,用于指明设备的类型,并提供一些额外的辅助功能。它的的形式如下:
1: /* include/linux/device.h, line 467 */ 2: struct device_type { 3: const char *name; 4: const struct attribute_group **groups; 5: int (*uevent)(struct device *dev, struct kobj_uevent_env *env); 6: char *(*devnode)(struct device *dev, umode_t *mode, 7: kuid_t *uid, kgid_t *gid); 8: void (*release)(struct device *dev); 9: 10: const struct dev_pm_ops *pm; 11: };
“
device_type的功能包括:
- name表示该类型的名称,当该类型的设备添加到内核时,内核会发出”DEVTYPE=‘name’”类型的uevent,告知用户空间某个类型的设备available了
- groups,该类型设备的公共attribute集合。设备注册时,会同时注册这些attribute。这就是面向对象中“继承”的概念
- uevent,同理,所有相同类型的设备,会有一些共有的uevent需要发送,由该接口实现
- devnode,devtmpfs有关的内容,暂不说明
- release,如果device结构没有提供release接口,就要查询它所属的type是否提供。用于释放device变量所占的空间
”
5.3 root device
在sysfs中有这样一个目录:/sys/devices,系统中所有的设备,都归集在该目录下。有些设备,是通过device_register注册到Kernel并体现在/sys/devices/xxx/下。但有时候我们仅仅需要在/sys/devices/下注册一个目录,该目录不代表任何的实体设备,这时可以使用下面的接口:
1: /* include/linux/device.h, line 859 */ 2: /* 3: * Root device objects for grouping under /sys/devices 4: */ 5: extern struct device *__root_device_register(const char *name, 6: struct module *owner); 7: 8: /* 9: * This is a macro to avoid include problems with THIS_MODULE, 10: * just as per what is done for device_schedule_callback() above. 11: */ 12: #define root_device_register(name) \ 13: __root_device_register(name, THIS_MODULE) 14: 15: extern void root_device_unregister(struct device *root);
该接口会调用device_register函数,向内核中注册一个设备,但是(你也想到了),没必要注册与之对应的driver(顺便提一下,内核中有很多不需要driver的设备,这是之一)。
以上がLinuxデバイスモデル(5)_デバイスとデバイスドライバーの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。