上一篇<<linux设备模型:kset及设备驱动抽象类(class)分析>>中分析了kset容器、class设备驱动抽象框架、class_kset用于类(class)的热插拔事件转到类子系统等内容,本篇继续分析设备模型bus、设备、驱动之间的联系。

bus(总线)是一种特殊的抽象框架,与class有着本质上的不同,class感觉上只是把一些核心组件聚集在一起,它主要为访问组件提供便利(如提供组件地址),而bus则是实实在在的功能性框架,它即可负责管理、维护驱动与设备之间的关系,也可作为主桥设备与硬件之间的访问通道等等。

如pci总线,当pci硬件设备插入卡槽后,首先通过pci_bus_type(pci总线操作结构) 中的 pm(电源操作结构)操作相关函数唤醒设备,然后通过pci_bus_match函数查找硬件设备是否被注册到驱动(通过pci_id表, id_table 指向驱动程序设备id表的指针),如果找到匹配的驱动将会调用probe函数(pci_driver文章中分析),执行驱动的探测函数,完成驱动的注册工作,届时这个pci硬件即可进入正常工作状态,它们之间的关系为:

                           主桥                                   ^                                                    |                                                   v                                    父bus                           ^                                               |                                   设备v                                  ^
pci硬件相关     <->           bus...            <->              |v 驱动

然后执行到pci_bus的pci_device_probe函数,通过主桥设备对象找到pin值(引脚号),计算出卡槽位置(槽号),然后通过槽号找到对应的中断号,写入到中断线(行)并关联到pci设备(由pci驱动使用),最后在连接设备的cpu上执行驱动程序初始化,执行驱动的探测函数(用户注册的驱动) 。

pci设备通过klist_devices列表(链表)记录pci驱动knode_driver(节点),可以理解为pci设备记录了一组动态pci驱动节点。

目录

1. 函数分析

1.1 buses_init

1.2 pci_driver_init

2. 源码结构

3. 部分结构定义

4. 扩展函数/变量

目录预览

1. 函数分析

1.1 buses_init

bus初始化函数这里首先创建bus_kset,关联了uevent操作,然后创建system_kset(表示子系统设备)

int __init buses_init(void)
{bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL); // 创建kset容器结构对象,设置根kobj名称,关联kobjs的uevent操作等...// 根kobj -> /sys/bus/if (!bus_kset)return -ENOMEM;system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj); // 创建kset容器,父kobj -> /sys/devices/ 的 子节点 kobj -> /sys/devices/system/if (!system_kset)return -ENOMEM;return 0;
}

bus_uevent_ops
devices_kset

1.2 pci_driver_init

注册总线类型,pci_bus_type、pcie_port_bus_type,注册dma通知链

static int __init pci_driver_init(void)
{int ret;ret = bus_register(&pci_bus_type); // 注册总线,通过pci_bus_type操作中的函数做到pci <-> 驱动 <-> 设备直接的互通关系// 注册总线,创建总线uevent,创建subsys_private私有结构(通过subsys(子系统)关联bustype/class结构的驱动程序,驱动绑定到设备列表中(knode_driver 加入到 klist_devices列表(链表)中))等等#ifdef CONFIG_PCIEPORTBUSret = bus_register(&pcie_port_bus_type); // 注册总线,pcie_port_bus_typeif (ret)return ret;
#endifdma_debug_add_bus(&pci_bus_type); // 注册bus通知链// 当驱动注册失败并且在dma的哈希列表中时(已分配dma地址), 调用dma_debug_device_change函数输出信息return 0;
}
postcore_initcall(pci_driver_init);

pci_bus_type
bus_register
pcie_port_bus_type

2. 源码结构

bus_uevent_ops kset与它相关的kobjects的uevent操作

static const struct kset_uevent_ops bus_uevent_ops = {.filter = bus_uevent_filter, // bus 用户空间事件过滤器函数,如果等于&bus_ktype,返回1// bus_ktype做了bus_sysfs_ops(.sysfs_ops,sysfs操作)和bus_release(.release,释放函数)赋值
};

pci_bus_type 设备的总线类型

struct bus_type pci_bus_type = {.name           = "pci", // 名称.match          = pci_bus_match, // 每当为此总线添加新设备或驱动程序时,可能会调用多次// 如果给定的设备可以由给定的驱动程序处理,它应该返回一个正值,否则返回零// 如果无法确定驱动程序支持该设备,它也可能返回错误代码// 在-EPROBE_DEFER的情况下,它将对设备排队等待延迟探测.uevent         = pci_uevent, // pci设备信息记录到环境缓冲区.probe          = pci_device_probe, // 探测函数.remove         = pci_device_remove,  // 移除设备.shutdown       = pci_device_shutdown, // 设备关闭.dev_groups     = pci_dev_groups, // 设备属性组.bus_groups     = pci_bus_groups, // bus属性组.drv_groups     = pci_drv_groups,  // 驱动属性组.pm             = PCI_PM_OPS_PTR, // 子系统级别和设备驱动程序级别的设备电源管理操作.num_vf         = pci_bus_num_vf, .dma_configure  = pci_dma_configure, // 使用来自主桥父节点(如果有)的OF节点或ACPI节点的相同信息更新PCI设备的DMA配置.dma_cleanup    = pci_dma_cleanup, // 设备驱动程序停止通过内核DMA API处理DMA设备// 它必须在iommu_device_use_default_domain()之后调用
};
EXPORT_SYMBOL(pci_bus_type);

pci_bus_match
pci_device_probe
PCI_PM_OPS_PTR

pcie_port_bus_type 设备的总线类型

struct bus_type pcie_port_bus_type = {.name           = "pci_express",.match          = pcie_port_bus_match,  // 每当为此总线添加新设备或驱动程序时,可能会调用多次
};
EXPORT_SYMBOL_GPL(pcie_port_bus_type);

PCI_PM_OPS_PTR 子系统级别和设备驱动程序级别的设备电源管理操作

static const struct dev_pm_ops pci_dev_pm_ops = {.prepare = pci_pm_prepare, // 防止在设备返回后注册新的子设备// 驱动程序的子系统和内核的其他部分通常应该在prepare()成功后防止对探测方法的新调用.complete = pci_pm_complete, // 设备从系统睡眠中恢复,刷新设备的电源状态数据.suspend = pci_pm_suspend, // 禁用PTM允许一些系统,进入低功率PM状态// 禁用PTM,但保留dev->ptm_enabled,以便在必要时在恢复时默默重新启用它.suspend_late = pci_pm_suspend_late, // 设备挂起.resume = pci_pm_resume, // 恢复设备.resume_early = pci_pm_resume_early, // 恢复设备的早期准备.freeze = pci_pm_freeze, // 冻结(创建快照),在创建系统内存的快照映像之前恢复所有运行时挂起的设备.thaw = pci_pm_thaw, // 特定于休眠,在创建休眠映像或映像创建失败后执行// 也在尝试从这样的图像恢复主存储器的内容失败后执行.poweroff = pci_pm_poweroff, // 特定于休眠,在保存休眠映像后执行。类似于suspend(),但它不需要将设备的设置保存到内存中.poweroff_late = pci_pm_poweroff_late, // 继续做poweroff()开始的操作。类似于suspend_late(),但它不需要在内存中保存设备的设置.restore = pci_pm_restore,  // 特定于休眠,在从休眠映像恢复主内存内容之后执行,类似于resume().suspend_noirq = pci_pm_suspend_noirq, // 完成由suspend()启动的操作// 执行挂起设备所需的任何附加操作,该设备可能正在与其驱动程序的中断处理程序竞争,在执行suspend_noirq()时,该程序保证不会运行.resume_noirq = pci_pm_resume_noirq, // 通过执行any为resume()的执行做准备.freeze_noirq = pci_pm_freeze_noirq, // 完成由freeze()启动的操作.thaw_noirq = pci_pm_thaw_noirq,.poweroff_noirq = pci_pm_poweroff_noirq,.restore_noirq = pci_pm_restore_noirq,.runtime_suspend = pci_pm_runtime_suspend, // 为设备做好准备,在这种情况下,由于电源管理,它将无法与CPU(s)和RAM通信.runtime_resume = pci_pm_runtime_resume, // 将设备置于完全活动状态,以响应硬件生成的唤醒事件或应软件的请求.runtime_idle = pci_pm_runtime_idle, // 设备似乎是不活动的,如果所有必要条件都得到满足,它可能被置于低功率状态
};#define PCI_PM_OPS_PTR  (&pci_dev_pm_ops)

3. 部分结构定义

device_driver 基本的设备驱动结构

struct device_driver {const char     *name; // 设备驱动程序的名称struct bus_type      *bus; // 驱动的设备所属的总线struct module        *owner; // 模块所有者const char      *mod_name;  /* 用于内置模块 */bool suppress_bind_attrs;   /* 禁用通过sysfs绑定/解除绑定 */enum probe_type probe_type; // 要使用的探测类型(同步或异步)const struct of_device_id   *of_match_table; // 打开的固件表const struct acpi_device_id   *acpi_match_table; // ACPI(高级配置与电源接口)匹配表int (*probe) (struct device *dev); // 查询特定设备是否存在,该驱动程序是否可以使用该设备,并将驱动程序绑定到特定设备void (*sync_state)(struct device *dev); // 在连接到该设备的所有状态跟踪消费者(在late_initcall时存在)成功绑定到驱动程序后,调用该函数将设备状态同步到软件状态// 如果设备没有消费者,该函数将在late_initcall_sync级别调用// 如果设备有从未绑定到驱动程序的消费者,则此函数将永远不会被调用,直到他们绑定到驱动程序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 attribute_group **dev_groups; // 设备实例绑定到驱动程序后附加的附加属性const struct dev_pm_ops *pm; // 匹配此驱动程序的设备的电源管理操作void (*coredump) (struct device *dev); // 当sysfs条目被写入时调用// 设备驱动程序需要调用dev_coredump API,从而产生一个ueventstruct driver_private *p; // 驱动核心的私有数据,除了驱动核心之外没有人可以碰它
};

pci_dev 结构描述了PCI设备

/*  */
struct pci_dev {struct list_head bus_list;  /* 每总线列表中的节点 */struct pci_bus   *bus;       /* 该设备所在的总线 */struct pci_bus    *subordinate;   /* 此设备桥接的总线 */void      *sysdata;   /* 用于特定于系统的扩展的钩子 */struct proc_dir_entry *procent;  /* /proc/bus/pci中的设备项 */struct pci_slot *slot;      /* 该设备所在的物理槽位 */unsigned int    devfn;      /* 编码的设备和功能(func)索引 */unsigned short    vendor; // 厂商IDunsigned short   device; // 设备ID unsigned short  subsystem_vendor; // 子系统厂商IDunsigned short  subsystem_device; // 子系统设备IDunsigned int    class;      /* 3 bytes: (base,sub,prog-if) */u8     revision;   /* PCI修订版本,类字的低字节 */u8       hdr_type;   /* PCI头类型(' multi'标志被屏蔽) */
#ifdef CONFIG_PCIEAERu16        aer_cap;    /* AER功能偏移 */struct aer_stats *aer_stats;   /* 该设备的AER统计 */
#endif
#ifdef CONFIG_PCIEPORTBUSstruct rcec_ea *rcec_ea;   /* RCEC缓存端点关联 */struct pci_dev  *rcec;          /* RCEC相关设备 */
#endifu32       devcap;     /* 作为PCIe设备功能 */u8      pcie_cap;   /* 作为PCIe功能偏移 */u8      msi_cap;    /* MSI功能偏移 */u8     msix_cap;   /* MSI-X功能偏移 */u8       pcie_mpss:3;    /* 支持的PCIe最大负载大小 */u8       rom_base_reg;   /* 配置寄存器控制ROM */u8      pin;        /* 此设备使用的中断引脚 */u16     pcie_flags_reg; /* 缓存PCIe功能寄存器 */unsigned long  *dma_alias_mask;/* 启用devfn别名的掩码 */struct pci_driver *driver;    /* 与此设备绑定的驱动程序 */u64        dma_mask;   // 本设备实现的总线地址位的掩码// 通常这是0xffffffff// 只有当设备破坏了DMA或支持64位传输时,才需要更改此设置.struct device_dma_parameters dma_parms; // dma设备参数pci_power_t current_state;  // 当前操作状态// 在ACPI中,这是D0-D3, D0是完全功能的,D3是关闭的unsigned int   imm_ready:1;    /* 支持立即准备 */u8      pm_cap;     /* 电源功能偏移 */unsigned int    pme_support:5;  /* 可生成pme状态的位掩码 */unsigned int  pme_poll:1; /* 轮询设备的PME状态位 */unsigned int   d1_support:1;   /* 支持低功耗D1 */unsigned int   d2_support:1;   /* 支持低功耗状态为D2 */unsigned int    no_d1d2:1;  /* D1和D2禁止使用 */unsigned int no_d3cold:1;    /* D3cold是被禁止的 */unsigned int   bridge_d3:1;    /* 允许D3用于桥 */unsigned int   d3cold_allowed:1;   /* D3cold是用户允许的 */unsigned int  mmio_always_on:1;   /* 禁止在BAR调整时关闭io/mem解码 */unsigned int   wakeup_prepared:1; // 唤醒准备unsigned int  skip_bus_pm:1;  /* 内部:跳过总线级PM */unsigned int    ignore_hotplug:1;   /* 忽略热插拔事件 */unsigned int   hotplug_user_indicators:1; /* 由sysfs用户单独控制的SlotCtl指示灯 */unsigned int    clear_retrain_link:1;   /* 需要手动清除再重新链接位 */unsigned int  d3hot_delay;    /* D3hot->D0 的转换时间(ms) */unsigned int    d3cold_delay;   /* D3cold->D0 的转换时间(ms) */#ifdef CONFIG_PCIEASPMstruct pcie_link_state   *link_state;    /* ASPM链接状态 */unsigned int  ltr_path:1; /* 从根到这里支持延迟容忍报告 */u16      l1ss;       /* L1SS能力指针 */
#endifunsigned int  pasid_no_tlp:1;     /* PASID在没有TLP前缀的情况下工作 */unsigned int   eetlp_prefix_path:1;    /* 端到端TLP前缀 */pci_channel_state_t error_state;  /* 当前连接状态 */struct device   dev;            /* 通用设备接口 */int     cfg_size;       /* 配置空间的大小 *//** 不要直接接触中断线和基址寄存器,而是使用存储在这里的值。他们可能是不同的*/unsigned int  irq;struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O和内存区域+扩展rom */bool      match_driver;       /* 跳过附加驱动程序 */unsigned int  transparent:1;      /* 减去解码桥 */unsigned int io_window:1;        /* 网桥有I/O窗口 */unsigned int  pref_window:1;      /* 桥有优先窗口 */unsigned int    pref_64_window:1;   /* Pref mem窗口为64位 */unsigned int    multifunction:1;    /* 多功能设备 */unsigned int is_busmaster:1;     /* 是总线主控装置 */unsigned int   no_msi:1;       /* 不可以使用MSI */unsigned int  no_64bit_msi:1;     /* 只能使用32位的msi */unsigned int   block_cfg_access:1; /* 配置空间访问被阻止 */unsigned int broken_parity_status:1; /* 生成假正奇偶校验 */unsigned int  irq_reroute_variant:2;  /* 需要IRQ重新路由变体 */unsigned int   msi_enabled:1;unsigned int  msix_enabled:1;unsigned int ari_enabled:1;      /* ARI转发 */unsigned int ats_enabled:1;      /* 地址转换Svc */unsigned int   pasid_enabled:1;    /* 进程地址空间ID */unsigned int  pri_enabled:1;      /* 页请求接口 */unsigned int is_managed:1;       /* 通过devres管理 */unsigned int    is_msi_managed:1;   /* MSI发布通过安装的devres */unsigned int  needs_freset:1;     /* 需要基本复位 */unsigned int    state_saved:1;unsigned int  is_physfn:1;unsigned int    is_virtfn:1;unsigned int    is_hotplug_bridge:1;unsigned int    shpc_managed:1;     /* shpchp拥有的SHPC */unsigned int is_thunderbolt:1;   /* Thunderbolt控制器 *//** 标记为不受信任的设备可能执行DMA攻击或类似攻击* 它们通常通过Thunderbolt等外部端口连接,但不限于此* 当启用IOMMU时,它们应该获得完整的映射,以确保它们不能访问任意内存*/unsigned int    untrusted:1;/** 来自平台的信息,例如ACPI或设备树,可能会将设备标记为“面向外部”* 面向外部的设备本身是内部的,但它的下游设备是外部的*/unsigned int    external_facing:1;unsigned int  broken_intx_masking:1;  /* 不使用INTx屏蔽 */unsigned int io_window_1k:1;     /* 英特尔网桥1K I/O窗口 */unsigned int irq_managed:1;unsigned int  non_compliant_bars:1;   /* 损坏的bar(可映射访问的硬件物理空间),忽略它们 */unsigned int  is_probed:1;        /* 正在进行设备探测 */unsigned int  link_active_reporting:1;/* 能够报告链路活动的设备 */unsigned int   no_vf_scan:1;       /* IOV启用后不扫描VF */unsigned int   no_command_memory:1;    /* 没有PCI_COMMAND_MEMORY */unsigned int  rom_bar_overlap:1;  /* ROM BAR损坏(或未使用) */pci_dev_flags_t dev_flags;atomic_t enable_cnt; /* 已调用pci_enable_device */u32       saved_config_space[16]; /* 挂起时保存的配置空间 */struct hlist_head saved_cap_space;int       rom_attr_enabled;   /* ROM属性显示 */struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* 资源的sysfs文件 */struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* 用于资源WC映射的sysfs文件 */#ifdef CONFIG_HOTPLUG_PCI_PCIEunsigned int  broken_cmd_compl:1; /* 某些命令没有完成 */
#endif
#ifdef CONFIG_PCIE_PTMu16       ptm_cap;        /* PTM能力 */unsigned int ptm_root:1;unsigned int ptm_enabled:1;u8        ptm_granularity;
#endif
#ifdef CONFIG_PCI_MSIvoid __iomem   *msix_base;raw_spinlock_t   msi_lock;
#endifstruct pci_vpd    vpd;
#ifdef CONFIG_PCIE_DPCu16       dpc_cap;unsigned int    dpc_rp_extensions:1;u8      dpc_rp_log_size;
#endif
#ifdef CONFIG_PCI_ATSunion {struct pci_sriov    *sriov;     /* PF: SR-IOV信息 */struct pci_dev        *physfn;    /* VF: 相关PF */};u16     ats_cap;    /* ATS能力偏移 */u8     ats_stu;    /* ATS最小翻译单元 */
#endif
#ifdef CONFIG_PCI_PRIu16        pri_cap;    /* PRI能力偏移 */u32        pri_reqs_alloc; /* 分配的PRI请求数 */unsigned int pasid_required:1; /* 需要PRG响应PASID */
#endif
#ifdef CONFIG_PCI_PASIDu16      pasid_cap;  /* PASID能力偏移 */u16      pasid_features;
#endif
#ifdef CONFIG_PCI_P2PDMAstruct pci_p2pdma __rcu *p2pdma;
#endifu16       acs_cap;    /* ACS能力偏移 */phys_addr_t    rom;        /* 物理地址(如果不是来自BAR) */size_t     romlen;     /* 长度(如果不是来自BAR) *//** 驱动程序名称来强制匹配* 不要直接设置,因为core会释放它* 使用driver_set_override()来设置或清除它*/const char    *driver_override;unsigned long  priv_flags; /* PCI驱动程序的私有标志 *//* 这些方法索引pci_reset_fn_methods[] */u8 reset_methods[PCI_NUM_RESET_METHODS]; /* 按优先顺序 */
};

pci_device_id PCI设备ID结构

struct pci_device_id {__u32 vendor, device;      /* 厂商ID和设备ID or PCI_ANY_ID */// #define PCI_ANY_ID (~0) 如果设置这个标志,应该是一个通用的设备,或者某厂商的产品使用通用的设备驱动__u32 subvendor, subdevice;  /* 子系统的厂商ID和设备ID or PCI_ANY_ID */__u32 class, class_mask;   /* (class,subclass,prog-if) triplet */// class: 要匹配的设备类、子类和“接口”// class_mask: 限制比较class字段的哪些子字段kernel_ulong_t driver_data;  /* 驱动程序的私有数据 */__u32 override_only; // 为true时,强制dev->driver_override为该驱动时匹配
};

pci_dynid 保存PCI设备动态ID的结构

struct pci_dynid {struct list_head node; // 设备节点列表struct pci_device_id id;
};

pci_bus PCI总线结构

struct pci_bus {struct list_head node;       /* 总线列表中的节点 */struct pci_bus    *parent;    /* 所在的桥(bridge)上的父总线 */struct list_head children;   /* 子总线列表 */struct list_head devices;    /* 此总线上的设备列表 */struct pci_dev   *self;      /* 父级看到的桥接设备 */struct list_head slots;      /* 总线上的槽的列表;  pci_slot_mutex互斥锁保护 */struct resource *resource[PCI_BRIDGE_RESOURCE_NUM];struct list_head resources;  /* 路由到此总线的地址空间 */struct resource busn_res;  /* 路由到此总线的总线编号 */struct pci_ops *ops;       /* 配置访问函数,操作 */void      *sysdata;   /* 用于特定于系统的扩展的钩子 */struct proc_dir_entry *procdir;  /* /proc/bus/pci中的目录条目 */unsigned char  number;     /* Bus号 */unsigned char primary;    /* 主桥数 */unsigned char  max_bus_speed;  /* enum pci_bus_speed */unsigned char   cur_bus_speed;  /* enum pci_bus_speed */
#ifdef CONFIG_PCI_DOMAINS_GENERICint        domain_nr;
#endifchar      name[48];unsigned short bridge_ctl; /* 管理NO_ISA/FBB/等行为 */pci_bus_flags_t bus_flags;    /* 由子总线继承 */struct device       *bridge;struct device       dev;struct bin_attribute    *legacy_io; /* 此总线的遗留I/O */struct bin_attribute *legacy_mem;    /* 遗留内存 */unsigned int      is_added:1;unsigned int     unsafe_warn:1;  /* 警告RW1C配置写入 */
};

pci_host_bridge pci主桥设备(连接CPU和PCI bus)结构

struct pci_host_bridge {struct device    dev;struct pci_bus  *bus;       /* Root bus */struct pci_ops    *ops;struct pci_ops *child_ops;void     *sysdata;int        busnr;int       domain_nr;struct list_head windows; /* resource_entry */struct list_head dma_ranges;    /* dma范围资源列表 */u8 (*swizzle_irq)(struct pci_dev *, u8 *); /* 平台IRQ源口 */int (*map_irq)(const struct pci_dev *, u8, u8);void (*release_fn)(struct pci_host_bridge *);void     *release_data;unsigned int  ignore_reset_delay:1;   /* 对于整个层次结构 */unsigned int  no_ext_tags:1;      /* 没有扩展标签 */unsigned int    native_aer:1;       /* 操作系统可能使用PCIe AER */unsigned int  native_pcie_hotplug:1;  /* 操作系统可能使用PCIe热插拔 */unsigned int   native_shpc_hotplug:1;  /* 操作系统可能使用SHPC热插拔 */unsigned int   native_pme:1;       /* 操作系统可能使用PCIe PME */unsigned int  native_ltr:1;       /* 操作系统可以使用PCIe LTR */unsigned int  native_dpc:1;       /* 操作系统可以使用PCIe DPC */unsigned int  preserve_config:1;  /* 保留固件资源设置 */unsigned int  size_windows:1;     /* 启用根总线大小调整 */unsigned int msi_domain:1;       /* 桥需要MSI域 *//* 资源调整要求 */resource_size_t (*align_resource)(struct pci_dev *dev,const struct resource *res,resource_size_t start,resource_size_t size,resource_size_t align);unsigned long   private[] ____cacheline_aligned;
};

pci_ops pci操作

struct pci_ops {int (*add_bus)(struct pci_bus *bus); // 添加总线void (*remove_bus)(struct pci_bus *bus); // 释放总线void __iomem *(*map_bus)(struct pci_bus *bus, unsigned int devfn, int where); // 映射int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val); // 读int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val); // 写
};

device_link 设备链接表示结构

struct device_link {struct device *supplier; // 链接厂商的设备struct list_head s_node; // 链接到厂商设备的消费者链接列表struct device *consumer; // 链接消费者端的设备struct list_head c_node; // 链接到消费者设备的厂商链接列表struct device link_dev; // 用于在sysfs中公开链接详细信息的设备enum device_link_state status; // 链接的状态(相对于驱动程序的存在)u32 flags; // Link标志refcount_t rpm_active; // 消费者设备是否处于运行时PM活动状态struct kref kref; // 计算重复添加相同链接的次数struct work_struct rm_work; // 用于删除链接的工作结构bool supplier_preactivated; /* 厂商在消费者探测前已激活 */
};

4. 扩展函数/变量

devices_kset 设备kset容器,根kobj -> /sys/devices/

/* /sys/devices/ */
struct kset *devices_kset;

pci_bus_match 判断一个PCI设备结构是否具有匹配的PCI设备id结构

/** dev 要匹配的PCI设备结构* drv 用于搜索匹配PCI设备id结构的设备驱动程序* * 驱动程序用来检查系统中的PCI设备是否在其支持的设备列表中* 返回匹配的pci_device_id结构,如果不匹配则返回%NULL* /
static int pci_bus_match(struct device *dev, struct device_driver *drv)
{struct pci_dev *pci_dev = to_pci_dev(dev);// #define to_pci_dev(n) container_of(n, struct pci_dev, dev)struct pci_driver *pci_drv; // /* 与此设备绑定的驱动程序 */const struct pci_device_id *found_id;if (!pci_dev->match_driver) // 跳过附加驱动程序return 0;pci_drv = to_pci_driver(drv);found_id = pci_match_device(pci_drv, pci_dev);if (found_id)return 1;return 0;
}

device_driver
pci_dev
pci_match_device

pci_match_device 查看pci设备是否匹配pci驱动程序的id列表

static const struct pci_device_id *pci_match_device(struct pci_driver *drv,struct pci_dev *dev)
{struct pci_dynid *dynid;const struct pci_device_id *found_id = NULL, *ids;/* * 当设置driver_override时,只绑定匹配的驱动* 某厂家的设备不支持通用的驱动情况下,应该设置这个标志*/if (dev->driver_override && strcmp(dev->driver_override, drv->name))return NULL;

pci_device_id
pci_dynid

/* 在静态id之前,先看看动态id */spin_lock(&drv->dynids.lock);list_for_each_entry(dynid, &drv->dynids.list, node) {if (pci_match_one_device(&dynid->id, dev)) { // 告诉一个PCI设备结构是否有匹配found_id = &dynid->id;break;}}spin_unlock(&drv->dynids.lock);if (found_id)return found_id;
||
\/
static inline const struct pci_device_id *
pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
{if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&(id->device == PCI_ANY_ID || id->device == dev->device) &&(id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&(id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&!((id->class ^ dev->class) & id->class_mask)) // 检查pci设备是否有匹配的驱动// 如果部分ID指定PCI_ANY_ID,表示这一部分属于通用的// 如id->vendor  == PCI_ANY_ID,表示这个pci设备可以是任意厂商的产品// 如id->vendor == dev->vendor,id->device == PCI_ANY_ID 表示这个厂商的多类pci设备产品可以使用自家的同一套驱动return id;return NULL;
}
for (ids = drv->id_table; (found_id = pci_match_id(ids, dev)); // id_table 指向驱动程序设备id表的指针ids = found_id + 1) { // 查看PCI设备是否匹配给定的pci_id表/** 匹配表基于driver_override进行拆分* 如果设置了override_only,则强制driver_override匹配*/if (found_id->override_only) {if (dev->driver_override)return found_id;} else {return found_id;}}/* Driver_override将始终匹配,发送一个虚拟id */if (dev->driver_override)return &pci_device_id_any;return NULL;
}

pci_device_probe 通过主桥设备对象找到pin值(引脚号),计算出卡槽位置(槽号),然后通过槽号找到对应的中断号,写入到中断线(行)并关联到pci设备(由pci驱动使用),最后在连接设备的cpu上执行驱动程序初始化,执行驱动的探测函数(用户注册的驱动)

static int pci_device_probe(struct device *dev)
{int error;struct pci_dev *pci_dev = to_pci_dev(dev);struct pci_driver *drv = to_pci_driver(dev->driver);if (!pci_device_can_probe(pci_dev)) // 不是虚拟设备 或者 驱动自动探测VF 或者 驱动始终匹配,继续执行return -ENODEV;pci_assign_irq(pci_dev); // 通过主桥设备对象找到pin值(引脚号),计算出卡槽位置(槽号),然后通过槽号找到对应的中断号,写入到中断线(行)并关联到pci设备(由pci驱动使用)// pci驱动 <-> 中断号

pci_assign_irq

     error = pcibios_alloc_irq(pci_dev); // arm64架构默认注册、生效中断(irq),x86等平台不做任何处理// 龙芯架构另做了msi判断,如果msi生效(独立的中断),则不生效中断(irq)...error = __pci_device_probe(drv, pci_dev);...return error;
}
||
\/
static int __pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev)
{const struct pci_device_id *id;int error = 0;if (drv->probe) {error = -ENODEV;id = pci_match_device(drv, pci_dev); // 查看pci设备是否匹配pci驱动程序的id列表if (id)error = pci_call_probe(drv, pci_dev, id); // 在连接设备的cpu上执行驱动程序初始化,执行驱动的探测函数(用户注册的驱动)}return error;
}

pci_call_probe

pci_assign_irq 通过主桥设备对象找到pin值(引脚号),计算出卡槽位置(槽号),然后通过槽号找到对应的中断号,写入到中断线(行)并关联到pci设备(由pci驱动使用)

void pci_assign_irq(struct pci_dev *dev)
{u8 pin;u8 slot = -1;int irq = 0;struct pci_host_bridge *hbrg = pci_find_host_bridge(dev->bus); // 找到根bus总线(所在的桥(bridge)上的父总线),返回主桥设备对象if (!(hbrg->map_irq)) {pci_dbg(dev, "runtime IRQ mapping not provided by arch\n");return;}

pci_find_host_bridge

     /** 如果这个设备不在主总线上,我们需要弄清楚它将进入哪个中断引脚* 我们知道它将进入哪一个插槽,因为该插槽是桥的所在位置* 每次中断线通过PCI-PCI桥时,我们都必须应用swizze函数*/pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin); // 通过PCI_OP_READ扩展的pci总线读函数,读一个字节// #define PCI_INTERRUPT_LINE      0x3c    /* 8 bits */// #define PCI_INTERRUPT_PIN       0x3d    /* 8 bits */// #define PCI_MIN_GNT             0x3e    /* 8 bits */// #define PCI_MAX_LAT             0x3f    /* 8 bits */

pci_read_config_byte

     /* 处理错误的pin值 */if (pin > 4)pin = 1;if (pin) {/* Follow the chain of bridges, swizzling as we go. */if (hbrg->swizzle_irq) // 平台IRQ源口(数据流)slot = (*(hbrg->swizzle_irq))(dev, &pin); // 得到槽号/** 如果没有使用swizzling函数,map_irq()必须忽略slot*/irq = (*(hbrg->map_irq))(dev, slot, pin); // 中断号if (irq == -1)irq = 0;}dev->irq = irq;/** 总是告诉设备,这样驱动程序就知道真正要使用的IRQ是什么* 设备不使用它*/pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq); // 通过PCI_OP_READ扩展的pci总线写函数,写一个字节
}

pci_write_config_byte

pci_find_host_bridge 找到根bus总线(所在的桥(bridge)上的父总线),返回主桥设备对象

pci_host_bridge *pci_find_host_bridge(struct pci_bus *bus)
{       struct pci_bus *root_bus = find_pci_root_bus(bus);
||
\/
static struct pci_bus *find_pci_root_bus(struct pci_bus *bus) // 找到根bus总线(所在的桥(bridge)上的父总线)
{while (bus->parent)bus = bus->parent;return bus;
}

pci_bus

return to_pci_host_bridge(root_bus->bridge); // 返回主桥设备对象
}
||
\/
#define to_pci_host_bridge(n) container_of(n, struct pci_host_bridge, dev)

pci_host_bridge

pci_read_config_byte 通过PCI_OP_READ扩展的pci总线读函数,读一个字节

EXPORT_SYMBOL(pci_bus_read_config_byte); // 读1个字节
EXPORT_SYMBOL(pci_bus_read_config_word); // 读一个字,2个字节
EXPORT_SYMBOL(pci_bus_read_config_dword); // 读两个字,4个字节
||
\/
PCI_OP_READ(byte, u8, 1)
PCI_OP_READ(word, u16, 2)
PCI_OP_READ(dword, u32, 4)
||
\/
#define PCI_OP_READ(byte, u8, 1) \
int noinline pci_bus_read_config_byte  (struct pci_bus *bus, unsigned int devfn, int pos, type *value)  // pci总线读函数扩展
{                                                                       int res;                                                        unsigned long flags;                                            u32 data = 0;                                                   if (PCI_byte_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;     // #define PCI_byte_BAD 0// #define PCI_word_BAD (pos & 1)// #define PCI_dword_BAD (pos & 3)/* PCI函数可能返回的错误值 */// #define PCIBIOS_SUCCESSFUL              0x00// #define PCIBIOS_FUNC_NOT_SUPPORTED      0x81// #define PCIBIOS_BAD_VENDOR_ID           0x83// #define PCIBIOS_DEVICE_NOT_FOUND        0x86// #define PCIBIOS_BAD_REGISTER_NUMBER     0x87// #define PCIBIOS_SET_FAILED              0x88// #define PCIBIOS_BUFFER_TOO_SMALL        0x89pci_lock_config(flags);                                         res = bus->ops->read(bus, devfn, pos, len, &data); // 主桥设备->pci_ops->读函数          if (res)                                                       PCI_SET_ERROR_RESPONSE(value);                         else                                                          *value = (u8)data;  // 数据通过value返回                                 pci_unlock_config(flags);                                      return res;
}

pci_ops

pci_write_config_byte 通过PCI_OP_READ扩展的pci总线写函数,写一个字节

EXPORT_SYMBOL(pci_bus_write_config_byte); // 写1个字节
EXPORT_SYMBOL(pci_bus_write_config_word); // 写一个字,2个字节
EXPORT_SYMBOL(pci_bus_write_config_dword); // 写两个字,4个字节
||
\/
PCI_OP_WRITE(byte, u8, 1)
PCI_OP_WRITE(word, u16, 2)
PCI_OP_WRITE(dword, u32, 4)
||
\/
#define PCI_OP_WRITE(size, type, len) \
int noinline pci_bus_write_config_byte \(struct pci_bus *bus, unsigned int devfn, int pos, type value)  // pci总线写函数扩展
{                                                                       int res;                                                        unsigned long flags;                                            if (PCI_byte_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;      pci_lock_config(flags);                                         res = bus->ops->write(bus, devfn, pos, len, value); // 主桥设备->pci_ops->写函数            pci_unlock_config(flags);                                       return res;
}

pci_call_probe 在连接设备的cpu上执行驱动程序初始化,执行驱动的探测函数(用户注册的驱动)

static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev,const struct pci_device_id *id)
{int error, node, cpu;struct drv_dev_and_id ddi = { drv, dev, id };/** 在连接设备的节点上执行驱动程序初始化* 这样,驱动程序可能会在右侧节点上分配其本地内存.*/node = dev_to_node(&dev->dev); // dev->numa_nodedev->is_probed = 1; // 设备需要探测cpu_hotplug_disable(); // cpu_hotplug_disabled++// 等待当前正在运行的CPU热插拔操作完成(如果有),并禁用将来的CPU热插拔(从sysfs)// “cpu_add_remove_lock”保护“cpu_hotplug_disabled”标志// 在执行热插拔操作之前,热插拔路径也会获取相同的锁// 因此,获取该锁可以确保当前正在运行的任何热插拔操作相互排斥/** 防止从物理设备的work_on_cpu() 探测Virtual Function设备时嵌套work_on_cpu()*/if (node < 0 || node >= MAX_NUMNODES || !node_online(node) ||pci_physfn_is_probed(dev)) { // numa节点不满足使用情况,或者是虚拟设备cpu = nr_cpu_ids; // cpu = 1} else {cpumask_var_t wq_domain_mask;if (!zalloc_cpumask_var(&wq_domain_mask, GFP_KERNEL)) { // 为&wq_domain_mask(指针)分配内存error = -ENOMEM;goto out;}cpumask_and(wq_domain_mask,housekeeping_cpumask(HK_TYPE_WQ),housekeeping_cpumask(HK_TYPE_DOMAIN));cpu = cpumask_any_and(cpumask_of_node(node),wq_domain_mask);free_cpumask_var(wq_domain_mask);}if (cpu < nr_cpu_ids)error = work_on_cpu(cpu, local_pci_probe, &ddi); // 在指定cpu上的线程上下文中运行local_pci_probeelseerror = local_pci_probe(&ddi); // 执行驱动的探测函数(用户注册的驱动)
out:dev->is_probed = 0;cpu_hotplug_enable(); // cpu热插拔恢复,cpu_hotplug_disabled--return error;
}

bus_register 注册总线,创建总线uevent,创建subsys_private私有结构(通过subsys(子系统)关联bustype/class结构的驱动程序,驱动绑定到设备列表中(knode_driver 加入到 klist_devices列表(链表)中))等等

int bus_register(struct bus_type *bus)
{int retval;struct subsys_private *priv; // 用于将private保存到bustype/class结构的驱动程序核心结构struct lock_class_key *key = &bus->lock_key;priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);priv->bus = bus; // 关联busbus->p = priv; // bus私有字段赋值BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier); // 初始化总线通知程序列表,用于显示任何与此总线上的事情有关的内容retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); // 设置与bus相同的名称priv->subsys.kobj.kset = bus_kset; // 关联bus_ksetpriv->subsys.kobj.ktype = &bus_ktype; // 关联bus_ktypepriv->drivers_autoprobe = 1; // 自动探测retval = kset_register(&priv->subsys); // 注册kset(子系统)retval = bus_create_file(bus, &bus_attr_uevent); // 创建bus文件,从kernfs_node_cache缓存中分配kn节点及初始化,将kn链接到同级rbtree,更新哈希值及时间戳,并激活这个节点

bus_attr_uevent

retval = bus_add_groups(bus, bus->bus_groups); // 获取kobj对象的sysfs所有权数据,创建kernfs_node节点及命名空间 (父级kn,如目录),然后为属性组->属性列表中的属性分配kernfs_node节点及初始化(子级kn,如目录/文件),包括关联kernfs_ops对象,将kernfs_node链接到同级rbtree,更新哈希值及时间戳,并激活这个节点(属性列表中的节点)
...
}
EXPORT_SYMBOL_GPL(bus_register);
priv->devices_kset = kset_create_and_add("devices", NULL,&priv->subsys.kobj); // 创建devices_kset容器priv->drivers_kset = kset_create_and_add("drivers", NULL,&priv->subsys.kobj); // 创建drivers_kset容器INIT_LIST_HEAD(&priv->interfaces);
__mutex_init(&priv->mutex, "subsys mutex", key);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); // 初始化klist 设备结构,get/put函数用于初始化获取和释放klist_node结构中记录的设备
klist_init(&priv->klist_drivers, NULL, NULL); // 初始化klist 驱动结构retval = add_probe_files(bus); // bus关联属性

bus_attr_drivers_probe

bus_attr_uevent 定义bus属性结构

static struct bus_attribute bus_attr_uevent = __ATTR(uevent, 0200, NULL,bus_uevent_store); // 定义bus属性结构
// name为"uevent",bus_uevent_store函数发送带参数的合成uevent事件

bus_attr_drivers_probe 用于驱动绑定到设备列表等功能的函数

bus_attr_drivers_probe
||
\/
static BUS_ATTR_WO(drivers_probe);
||
\/
#define BUS_ATTR_WO(drivers_probe) \struct bus_attribute bus_attr_drivers_probe = __ATTR_WO(drivers_probe)
||
\/
struct bus_attribute bus_attr_drivers_probe = {                                             .attr   = { .name = "drivers_probe", .mode = 0200 },        .store  = drivers_probe_store,
};
||
\/
static ssize_t drivers_probe_store(struct bus_type *bus,const char *buf, size_t count)
{struct device *dev;int err = -EINVAL;dev = bus_find_device_by_name(bus, NULL, buf); // 设备迭代器中找到对应的设备//bus的私有结构中匹配klist_devicesif (!dev)return -ENODEV;if (bus_rescan_devices_helper(dev, NULL) == 0) // 驱动绑定到设备列表等功能的函数err = count;put_device(dev);return err;
}

device_bind_driver

device_bind_driver 驱动加入到设备列表中(knode_driver 加入到 klist_devices列表(链表)中),deferred_probe列表用于重试一次,这是安全的

int device_bind_driver(struct device *dev)
{int ret;ret = driver_sysfs_add(dev); // 驱动私有结构的kobj与设备kobj之间,互相创建不同名称的链接文件,创建coredump属性(coredump_store函数)if (!ret) {device_links_force_bind(dev); // 为强制绑定设备做准备,检查厂商是否存在(硬件)driver_bound(dev); // 驱动加入到设备列表中(knode_driver 加入到 klist_devices列表(链表)中),deferred_probe列表用于重试一次,这是安全的}else if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_DRIVER_NOT_BOUND, dev); // 调用阻塞通知器链中的函数// 无法绑定驱动程序通知return ret;
}
EXPORT_SYMBOL_GPL(device_bind_driver);

driver_sysfs_add
device_link

driver_sysfs_add 发送准备驱动绑定通知,驱动私有结构的kobj与设备kobj之间,互相创建不同名称的链接文件,创建coredump属性(coredump_store函数)

static int driver_sysfs_add(struct device *dev)
{int ret;if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_BIND_DRIVER, dev); // 调用阻塞通知器链中的函数// #define BUS_NOTIFY_BIND_DRIVER          0x00000004 /* 准备驱动绑定通知 */ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,kobject_name(&dev->kobj)); // 在两个对象之间创建符号链接// dev->driver->p->kobj 链接到 dev->kobjret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,"driver"); if (!IS_ENABLED(CONFIG_DEV_COREDUMP) || !dev->driver->coredump)return 0;ret = device_create_file(dev, &dev_attr_coredump); // static DEVICE_ATTR_WO(coredump);// coredump_storeif (!ret)return 0;...
}

目录预览

<<linux设备模型:sysfs(kobject)解析>>
<<linux设备模型:kset及设备驱动抽象类(class)分析>>

linux设备模型:bus概念及pci_bus分析相关推荐

  1. linux设备模型之kset/kobj/ktype分析

    1. 概述 今天来聊一下Linux设备模型的基石:kset/kobject/ktype. sysfs文件系统提供了一种用户与内核数据结构进行交互的方式,可以通过mount -t sysfs sysfs ...

  2. linux设备模型bus,device,driver,(kobject、ktype、kset,bus_type、device、device_driver)

    1.1Linux设备驱动模型简介 1.什么是设备驱动模型 (1)类class.总线bus(负责将设备和驱动挂接起来).设备devices.驱动driver(可以看到在驱动源码中,不管是什么样的驱动,都 ...

  3. LINUX设备模型BUS,DEVICE,DRIVER

    虽然看了上面一篇转载的<使用/sys/访问系统>对总线,驱动,设备都讲得比较细但还是没有太多的感觉.在此就先把自己今天所学回忆一下. 为了满足新的要求,linux2.6提供了新的设备模型: ...

  4. linux设备模型之tty驱动架构分析,linux设备模型之uart驱动架构分析

    五: uart_add_one_port()操作本文引用地址:http://www.eepw.com.cn/article/201610/305916.htm 在前面提到.在对uart设备文件过程中. ...

  5. linux设备模型:devtmpfs虚拟文件系统分析

    devtmpfs是一个设备文件系统,它将其所有文件保存在虚拟内存中. devtmpfs中的所有内容都是临时的,因为不会在您的硬盘驱动器上创建任何文件.如果卸载devtmpfs实例,其中存储的所有内容都 ...

  6. linux设备模型:pci驱动程序注册过程

    一个具有正常探测功能的pci驱动程序应具有基本的pci_driver结构实现,如: static struct pci_driver driver_ops = {.name = "drive ...

  7. Linux设备模型分析之kset

    上一篇博客我们分析了Linux设备模型中kobject的注册和使用,在这一篇文章中,我们来看一下kset的用法. 首先我们看一个使用kset的例子,代码如下: [cpp] view plaincopy ...

  8. linux lddbus设备,Linux设备驱动程序学习(14)-Linux设备模型(各环节的整合)

    Linux设备驱动程序学习(14) -Linux设备模型(各环节的整合) 通过一个设备在内核中生命周期的各个阶段,可以更好地理解Linux设备模型.我将通过分析lddbus和sculld的源码来了解L ...

  9. Linux设备模型分析之kobject(基于3.10.1内核)

    一.kobject结构定义 kobject是Linux设备模型的最底层数据结构,它代表一个内核对象. kobject结构体定义在include/linux/kobject.h文件中: [cpp] vi ...

最新文章

  1. 史上最全数据结构算法之递归系列学习,建议收藏!
  2. 关于Linux前后台程序切换
  3. HDU 1047 Integer Inquiry( 高精度加法水 )
  4. (转载)Android游戏开发之旅一 长按Button原理
  5. Yabbly:让经验缔结因果
  6. Hihocoder 1370 快乐数字
  7. 软件定义存储的特征及如何工作
  8. (99)FPGA最大延迟与最小延迟基础
  9. java 数据类型之原码,补码,反码
  10. C++的隐式转换和explicit关键字
  11. apache php 调优_Apache的性能优化实例(一)
  12. Windows窗体编程基础学习:更改TabControl 的外观(如qq用的)
  13. 数据库接口实验--php实现--
  14. 应届java开发简历,一文全懂
  15. Mac安装telnet工具和使用
  16. Comware 架构理解
  17. 弘辽科技:淘宝保险保证金怎么开通?它和消保保证金有什么区别?
  18. 鸿蒙车机系统合作,鸿蒙OS车机系统来了!华为吉利合作曝光,博越Pro或率先用上...
  19. 基于 Node.js + Koa 构建完整的 Web API (配置 ESLint 和使用 Airbnb 编码规范)
  20. CAD中插入外部参照字体会变繁体_提高CAD绘图效率,外部参照你真的懂?

热门文章

  1. 基于视觉的3D Occupancy论文阅读:MonoScene等最新文章!
  2. 2021年陕西省安全员C证报名考试及陕西省安全员C证找解析
  3. n阶非零矩阵AB,矩阵AB=0,则A和B的秩都小于n
  4. 快消品行业供应链管理解决方案
  5. Java SpringBoot框架依赖汇总
  6. Deep Leakage from Gradients
  7. Java行业已经饱和了?胡扯,怎么可能,只是不需要这类型的程序员了
  8. 现在Java主流技术有哪些?
  9. 罗胖精选 | 练写作,从日记开始吧!| 心得体会
  10. 智慧公寓云系统解决方案