linux 驱动模型的简单分析

为什么需要设备模型

以前设备的都是的冷插拔的,设备单一,所以在设计程序时无需考虑设备的动态接入系统的情况,也无需考虑能源的损耗。随着热插拔的发展,设备呈现多样化,设备的拓扑结构越来越复杂。所以需要,建立一个能够对所有的设备进行抽象的管理统一的模型。所以在linux2.6版本中建立了设备模型来管理的进行设备的管理(其实是为了省电)。。

设备模型的任务

1.电源管理和系统关机
需要对系统上的设备进行电源管理,就需要对设备与设备之间建立关系,设备在进行断电时需要按照一定的顺序进行。不能够随意的直接在根设备上直接断电。
2.与用户空间的通讯
在用户空间来和系统的设备进行交互也是有必要的,所以需要为了内核空间和用户空间提供通信的接口。
3.支持热插拔
动态的接入设备能够正常的工作。
4.资源管理(地址,irq线,DMA通道,存储区),包括对设备的资源分配和解决可能出现的冲突。
对于一些固定的设备需要进行系统资源的分配工作。
5.设备的分类
设备的分类能够带来更好的设备管理和使用
6.对于对象的生命周期的管理。
由于需要完成上述的功能,支持热插拔和用户层的显示,内核会对系统资源进行操作,需要实现对设备在内核中的抽象的描述管理

设备模型如何产生

现实的生活中的设l备都是具体的实物,一切皆为对象,所以采用面向对象的设计,能够更好的描述物理设备到内核中。设备在物理结构上的拓扑关系为树的结构,所以在设计模型的时可以借用这样的关系。在设备的角度,每个设备需要一个特定的驱动,但是设备并不是都不相同。在设计模型中设备与它对的驱动进行分离。设备必须要有对应的驱动才能够正常的工作,因而需要,一个纽带来连接设备和驱动,这时实际的计算机体系中的总线,就很好的处理了设备和处理器的关系。因此,设备模型中的外部表现为:总线,设备,驱动。

linux系统的驱动模型

1.linux设备模型可以划分为三层:
1.设备和驱动层:具体的物理设备在内核中描述,具体的物理设备驱动程序的实现
2.中间层:把物理设备和驱动注册到内核中并连接内核感知层,并实现设备与驱动的连接
3.内核感知层:管理物理设备与驱动注册到内核中的设备和驱动,并在用户空间显示其状态信息。
第一层是由驱动开发人员开发实际的驱动程序,第二层就是内核中的设备模型 对设备和驱动在内核中的高层抽象,也就是总线,设备,驱动,类。并提供驱动开发的所需的接口,第三层是内核对设备与驱动的更高级的抽象,完成设备模型中对对象的管理。
中间层的抽象:总线,设备,驱动,类

BUS
总线是cpu和和各个设备之间通信的通道,为了方便设备模型的抽象,所有的设备都应连接到总线上(无论是CPU内部总线、虚拟的总线还是“platform Bus”)。

Class
在Linux设备模型中,它主要是集合具有相似功能或属性的设备,这样就可以抽象出一套可以在多个设备之间共用的数据结构和接口函数。因而从属于相同Class的设备的驱动程序,就不再需要重复定义这些公共资源,直接从Class中继承即可。

Device
抽象系统中所有的硬件设备,描述它的名字、属性、从属的Bus、从属的Class等信息。

device driver
Linux设备模型用Driver抽象硬件设备的驱动程序,它包含设备初始化、电源管理相关的接口实现。而Linux内核中的驱动开发,基本都围绕该抽象进行(实现所规定的接口函数)。

注:什么是Platform Bus?
在计算机中有这样一类设备,它们通过各自的设备控制器,直接和CPU连接,CPU可以通过常规的寻址操作访问它们(或者说访问它们的控制器)。这种连接方式,并不属于传统意义上的总线连接。但设备模型应该具备普适性,因此Linux就虚构了一条Platform Bus,供这些设备挂靠。
设备模型的中心就是:总线、设备、驱动都有自己的专属结构,设备和驱动是靠总线作为枢纽.
由于总线,设备和驱动的多样性,所以一般在第一层的驱动开发的过程中,会对中间层的抽象进行更加具体的实例化封装。因此,中间层的总线,设备和驱动是具体的设备与驱动在内核在中的抽象表现。
总线抽象
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root; 总线也是设备(总线的默认父设备)
struct device_attribute dev_attrs; / use dev_groups instead */
const struct attribute_group **bus_groups;总线属性
const struct attribute_group **dev_groups;设备属性
const struct attribute_group **drv_groups;驱动属性

    int (*match)(struct device *dev, struct device_driver *drv);匹配函数
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);热插拔
    int (*probe)(struct device *dev);探测函数
    int (*remove)(struct device *dev);移除函数
    void (*shutdown)(struct device *dev);关机函数

    int (*online)(struct device *dev);
    int (*offline)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);挂起函数
    int (*resume)(struct device *dev);

    const struct dev_pm_ops *pm;对总线上电源管理

    const struct iommu_ops *iommu_ops;

    struct subsys_private *p;管理设备和驱动的数据结构
    struct lock_class_key lock_key;

};
struct subsys_private {
struct kset subsys;该总线所在的子系统
struct kset *devices_kset;该总线上的所有设备的集合
struct list_head interfaces;
struct mutex mutex;

    struct kset *drivers_kset;该总线上的所有驱动的集合
    struct klist klist_devices;该总线上的设备链表
    struct klist klist_drivers;该总线上的驱动链表
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;
    struct bus_type *bus;

    struct kset glue_dirs;
    struct class *class;

};
通过上面数据结构体可以看出每个总线是一个子系统,总线的上保存着改下总线上的所有的设备和驱动。设备和驱动在加入到总线上时,通过总线的match函数按照一定的规则进行匹配。匹配完成之后,会调用驱动的probe函数,进行该设备的资源的申请和绑定及其设备的初始化化。 同时总线上还有一系列的电源管理的函数和热插拔函数。
总线也是一种设备,设备要在内核中使用,那么需要对进行注册,总线注册接口为bus_register(),其主要的功能是在sys文件系统中创建相关的节点和填充struct subsys_private 结构体。

pci总线的实例:
struct bus_type pci_bus_type = {
.name = “pci”,
.match = pci_bus_match,
.uevent = pci_uevent,
.probe = pci_device_probe,
.remove = pci_device_remove,
.shutdown = pci_device_shutdown,
.dev_groups = pci_dev_groups,
.bus_groups = pci_bus_groups,
.drv_groups = pci_drv_groups,
.pm = PCI_PM_OPS_PTR,
.num_vf = pci_bus_num_vf,
.dma_configure = pci_dma_configure,
};
int bus_register(struct bus_type *bus);注册
int bus_unregister(struct bus_type *bus);注销

设备抽象
struct device {
struct device *parent; //设备的层次

    struct device_private   *p;//设备模型中的核心使用,在内核中的设备描述

    struct kobject kobj;      //生命周期的实体
    const char              *init_name; /* initial name of the device */
    const struct device_type *type; 设备类型

    struct mutex            mutex;  /* mutex to synchronize calls to
                                     * its driver.
                                     */

    struct bus_type *bus;           /* type of bus device is on */s所属总线
    struct device_driver *driver;   /* which driver has allocated this 
                                       device */指定的驱动
	/* 设备驱动使用的私有数据成员*/
    void            *platform_data; /* Platform specific data, device
                                       core doesn't touch it */
    void            *driver_data;   /* Driver data, set and get with
                                       dev_set/get_drvdata */
    struct dev_pm_info      power;
    struct dev_pm_domain    *pm_domain;

#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif

#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 dma_mask; / dma mask (if dma’able device) /
u64 coherent_dma_mask;/
Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
unsigned long dma_pfn_offset;

    struct device_dma_parameters *dma_parms;

    struct list_head        dma_pools;      /* dma pools (if dma'ble) */

    struct dma_coherent_mem *dma_mem; /* internal for coherent mem
                                         override */

#ifdef CONFIG_DMA_CMA
struct cma cma_area; / contiguous memory area for dma
allocations /
#endif
/
arch specific additions */
struct dev_archdata archdata;

    struct device_node      *of_node; /* associated device tree node */
    struct acpi_dev_node    acpi_node; /* associated ACPI device node */

    dev_t                   devt;   /* dev_t, creates the sysfs "dev" */设备号
    u32                     id;     /* device instance */

    spinlock_t              devres_lock;
    struct list_head        devres_head;

    struct klist_node       knode_class;
    struct class            *class;			//该设备属于什么类
    const struct attribute_group **groups;  /* optional groups */

    void    (*release)(struct device *dev); //实例设备的释放
    struct iommu_group      *iommu_group;

    bool                    offline_disabled:1;
    bool                    offline:1;

};
device 一般是嵌入到具体的设备中进行使用的。
如:struct pci_dev {
struct list_head bus_list; /* Node in per-bus list */
struct pci_bus bus; / Bus this device is on */
struct pci_bus subordinate; / Bus this device bridges to */
void *sysdata;
………
}
devices_kset:代表的是/sys/bus/pci/device;
dev->kobj.kset = devices_kset;设定设备所属的kset(/sys/bus/pci/driver/目录)。因此,从这里体现了设备的层次。
kobject_init(&dev->kobj, &device_ktype);设定该对象的属性
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);设备加入总线中
从设备结构体中记录着该设备属于什么总线和分配的驱动。
设备的注册函数:
device_register(struct device *dev);
device_unregister(struct device *dev);
注册函数函数会在/sys/devices下创建相应的目录。

驱动抽象结构体
struct device_driver {
const char *name;驱动名字
struct bus_type *bus;所属总线

    struct module           *owner;
    const char              *mod_name;      /* used for built-in modules */

    bool suppress_bind_attrs;       /* disables bind/unbind via sysfs */ 手动绑定设备和驱动

    const struct of_device_id       *of_match_table;驱动支持的设备表
    const struct acpi_device_id     *acpi_match_table;

    int (*probe) (struct device *dev); 
    int (*remove) (struct device *dev);
    void (*shutdown) (struct device *dev);
    int (*suspend) (struct device *dev, pm_message_t state);
    int (*resume) (struct device *dev);
    const struct attribute_group **groups;

    const struct dev_pm_ops *pm;

    struct driver_private *p;

};

struct driver_private {
struct kobject kobj;
struct klist klist_devices;//该驱动可以驱动的设备
struct klist_node knode_bus;//链接到bus的驱动链表中的节点
struct module_kobject *mkobj;//内核module相关变量
struct device_driver *driver;
};
device_driver驱动结构体一般嵌入到一个具体的设备中使用。
例如pci驱动在内核中的实例:
struct pci_driver {
struct list_head node;
const char *name;
const struct pci_device_id id_table; / Must be non-NULL for probe to be called */
int (*probe)(struct pci_dev *dev, const struct pci_device_id id); / New device inserted */
void (*remove)(struct pci_dev dev); / Device removed (NULL if not a hot-plug capable driver) */
int (*suspend)(struct pci_dev dev, pm_message_t state); / Device suspended */
int (*suspend_late)(struct pci_dev *dev, pm_message_t state);
int (*resume_early)(struct pci_dev *dev);
int (*resume) (struct pci_dev dev); / Device woken up */
void (*shutdown) (struct pci_dev *dev);
int (*sriov_configure) (struct pci_dev dev, int num_vfs); / On PF */
const struct pci_error_handlers *err_handler;
const struct attribute_group **groups;
struct device_driver driver;
struct pci_dynids dynids;
};
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);将驱动实体加入到总线的驱动链表中。
priv->kobj.kset = bus->p->drivers_kset;设定驱动所属的kset(/sys/bus/pci/driver/目录)。因此,从这里体现了驱动的层次。

驱动和设备的匹配需要联系上靠的总线,所以驱动需要注册到总线上。pci_driver结构体中的id_table记录着该驱动能够支持的设备。pci_driver结构体中的probe函数会在设备和驱动注册时,驱动和设备匹配成功后被系统调用进行系统资源的分配。
例如e1000e驱动的注册:
 pci_register_driver(&e1000_driver);
pci_unregister_driver(&e1000_driver);


设备模型的内核感知层:kobject,kset,ktype

struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node sd; / sysfs directory entry */
struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
kobject 内核对象的基类。 由于内核在不知道新的接入的设备是什么,所以采用一个内核基类对象来描述具体的对象。所有对象继承该基类,内核就完成设备模型中的功能重合的部分,如设备的生命周期的管理。
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
} __randomize_layout;
kset:代表这相同类的对象的集合。(集合:按照类型进行细分,/sys/bus 、/sys/bus/pci 、/sys/bus/pci/device/ )

kobj_kset_join(struct kobject *kobj)
->list_add_tail(&kobj->entry, &kobj->kset->list);

struct kobj_type {
void (*release)(struct kobject *kobj);//释放kobject的数据结构。
const struct sysfs_ops *sysfs_ops;//该对象的sysyfs文件系统的接口
struct attribute **default_attrs;//该对象的属性列表(sysfs文件系统下的文件)
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};
release:描述kobject的对象的类型,释放kobjec对象。
kobj_type:表示的对象在sysfs文件系统的属性。

这里面包含了Linux设备模型中最基本的元素:kobject和kset。
kobject是所有内核对象的基类,所实现的只是一些公共的接口,kset是同种类型kobject对象的集合,可以说是对象的容器。
在这里插入图片描述 这样内核使用kobject将各个对象连接起来组成了一个分层的结构体系,kobject包含了parent成员,kset使用链表来实现。

设备模型中sysfs文件系统中的用户接口,一个kset,kobject代表着/sys下的一个目录。 其目录的位置是由是该对应的kobject在所在的位置所决定的, 如果kobject的parent的父对象被设置,则在该父目录下,如果kobject的parent未被设置,而该kset→kobj被设置,则目录在kset→kobj中,两者未被设置,目录在/sys的根目录下。所以kset和kobject搭建起了设备的层次,设备,驱动,总线不同的属性对应该的操作性。

设备和驱动如何绑定

在linux的设备模型中, 设备和驱动都必须依附于一种总线。在设备和驱动中都包含该设备或者驱动所属的总线,设备和驱动分开注册到总线上。因此在注册驱动或者设备时,不必需要设备和驱动的存在。在驱动或者设备在注册到总线时,通过总线上的match()成员函数将两者绑定到一起。
E1000e网卡驱动的注册和探测:
/drivers/net/ethernet/intel/e1000e/netdev.c:
module_init(e1000_init_module);
e1000_init_module(void)->pci_register_driver(&e1000_driver)

/include/linux/pci.h:
#define pci_register_driver(driver)
__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
/drivers/pci/pci-driver.c:

int __pci_register_driver(struct pci_driver *drv, struct module *owner,
const char mod_name)
{
/
initialize common driver fields /
drv->driver.name = drv->name;
drv->driver.bus = &pci_bus_type;
drv->driver.owner = owner;
drv->driver.mod_name = mod_name;
drv->driver.groups = drv->groups;
spin_lock_init(&drv->dynids.lock);
INIT_LIST_HEAD(&drv->dynids.list);
/
register with core */
return driver_register(&drv->driver);
}
/drivers/base/driver.c:

driver_register(struct device_driver *drv)
-> driver_find(drv->name, drv->bus);在pci总线上按驱动名字寻找驱动。返回 driver_private
数据指针 *priv;实际是为了检测是否注册该驱动。
->bus_add_driver(drv);添加驱动到总线上。
-> driver_attach(drv);
-> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
->__driver_attach(struct device *dev, void *data) ;data 为设备驱动。在这里每一个设备和该驱动进行匹配。
-> driver_match_device(drv, dev);

static inline int driver_match_device(struct device_driver *drv,struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
struct device_driver 被包含在e1000_dirver中,并在__pci_register_driver()函数中进行赋值。

在 driver_match_device(struct device_driver *drv,struct device *dev)函数中的 drv->bus->match(dev,drv)实际是pci总线上的pci_bus_match函数。

drv->bus->match(dev,drv);根据dev获取pci总线上注册的pci_device。检测该设备是否有匹配的设备驱动。 根据drv从总线上获取pci_driver,这里是e1000_driver驱动。
drv->bus->match(dev,drv);
->pci_match_device(pci_drv, pci_dev);

static const struct pci_device_id *pci_match_device(struct pci_driver *drv,struct pci_dev *dev);函数匹配步骤有下面3个

1.这个函数首先检测该设备是否有指定的设备驱动,有则进行和将要匹配的设备驱动的名称比较。接着匹配动态的id,然后是静态的id,找到对应的设备和设备驱动时返回设备id。未找到返回&pci_device_id_any;
2.匹配动态的id的函数为pci_match_one_device(&dynid->id, dev),这里匹配的方式在动态的id表中和pci_dev进行匹配。匹配的方式是比较设备和设备驱动vendor,device,subvendo,subdevice是否一致。
3.在动态的ids表,没有找到pci设备id,然后在静态(设备驱动的id_table)中找是否含该设备。

pci_match_id(drv->id_table, dev);drv->id_table是该驱动列举的可以支持的设备。
在找到之后函数回到__driver_attach(struct device *dev, void *data)中执行driver_probe_device(drv, dev);在这里进行驱动和设备的绑定。
下面进入设备和设备驱动的探测
driver_probe_device(struct device_driver *drv, struct device *dev)
->really_probe(dev, drv);
->dev->bus->probe(dev)
-> __pci_device_probe(drv, pci_dev)
-> pci_call_probe(drv, pci_dev, id);
-> local_pci_probe(&ddi);
-> pci_drv->probe(pci_dev, ddi->id);
->driver_bound(dev);驱动与设备的绑定
->klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);

在really_probe(dev, drv)中,如果总线上有probe()函数,就调用该函数dev->bus->probe(dev),实际是这个static int pci_device_probe(struct device *dev)函数。
函数pci_drv->probe(pci_dev, ddi->id)实际是e1000e_driver中的e1000_probe函数。这个函数的内容有很多。只是简单看了,获取硬件信息,初始化硬件设备。注册net_device,等。这个是链接网络协议栈的枢纽。
driver_bound(dev)函数是将设备和驱动进行绑定,所谓的绑定将设备的private结构体中的knode_driver节点加入的到与该设备绑定的驱动的private结构体的kilst_devices链表中。本质是通过数据结够将两者建立联系。

pci设备如何注册(非热插拔)

start_kernel()->reset_init() //在这里开始创建kernel_init进程啦!
kernel_init()->kernel_init_freeable()->do_basic_setup()->do_initcalls() ;

/arch/x86/kernel/x86_init.c:
struct x86_init_ops x86_init平台提供默认的函数,用于硬件资源的探测。
/arch/x86/pci/init.c:
static __init int pci_arch_init(void);
这个函数的根据不同的配置类型,选择不同的探测方法,探测方法有两种,一种是:linux直接探测(这种方法是linux内核自己去探测),二种是:PCI BIOS的方法,这种需要BIOS支持PCI总线,但是,不是所有的的平台都支持PCI BIOS的,所以linux的默认配置是了linux直接探测法。该函数是针对平台架构进行初始化。主要是申请PCI配置空间的IO端口0xCF8~0xCFF,注册正确的的读取PCI设备配置空间的方法。
直接探测:
从pci配置空间上可以看出子总线和设备是有限的,最多有256根总线,每个总线上只有32个设备,且设备的连接方式采用的是树形结构,所以在探测设备时,使用的是循环扫描的每一根总线和总线上的设备。

/arch/x86/pci/legacy.c:
subsys_initcall(pci_subsys_init);
pci_subsys_init(void)
->pci_legacy_init()
->pcibios_scan_root(0);
-> pci_scan_root_bus();
->pci_create_root_bus()
->pci_scan_child_bus() 扫描总线,
-> pci_scan_child_bus_extend(bus, 0);
-> pci_scan_slot(bus, devfn);每次扫描8个
->pci_scan_single_device(bus, devfn);
->pci_scan_device(bus, devfn);
->pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000))
->pci_setup_device(dev)
->pci_device_add(dev, bus);
->device_add(&dev->dev);添加设备到对应的总线上。
pci_scan_root_bus(NULL, busnum, &pci_root_ops, sd, &resources);扫描pci设备和总线。

总结:linux设备驱动模型这里采用的面向对象的设计方式,十分的极致,所有的设备和驱动对于内核一切都是对象,内核在管理时不需要知道对象具体代表是什么设备或者驱动。同时有采用软件分层设计,使复杂多样的问题呈现出层次化,具体化,每一层解决本层次的问题。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值