精华内容
下载资源
问答
  • Linux内核总线左手牵着驱动右手牵着设备,总线的工作就是就是完成总线下的设备和驱动之间的匹配。也就是在左手中找到与右手相匹配的设备驱动,并完成他们之间的匹配。 2、Linux内核总线的表现形式 Linux系统...

    @TOC

    1、Linux内核中的总线作用

    在Linux内核中,总线属于核心层,是纯软件的东西。在Linux内核中总线左手牵着驱动右手牵着设备,总线的工作就是就是完成总线下的设备和驱动之间的匹配。也就是在左手中找到与右手相匹配的设备驱动,并完成他们之间的匹配。

    在这里插入图片描述

    2、Linux内核中总线的表现形式

    Linux系统内核使用bus_type结构体表示总线,此结构体定义在文件include/linux/device.h,具体内容如下:

    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;
    };
    

    对用的文件显示如下:
    在Linux系统中,文件的体现形式是在“/sys/bus”体现的,如下:

    在这里插入图片描述
    在Ubuntu中bus目录下对应着许多具体的驱动,我们随便进入一个具体的bus下查看一下:
    在这里插入图片描述
    在其目录下就对应着有设备及其驱动两个文件夹,分别存放着具体的设备及其驱动。

    展开全文
  • Linux内核平台总线设备驱动模型浅析

    千次阅读 2017-08-22 16:10:12
     一、Linux系统的驱动框架的基础很大一部分是围绕着总线设备驱动模型展开的。 二、涉及3个重要结构体:struct bus_type:总线struct device :设备struct device_driver:驱动 三、结构体核心代码分析(2.6.38内核...

    复习总线设备驱动模型,做了一点小笔记,大牛略过。

     

    一、Linux系统的驱动框架的基础很大一部分是围绕着总线设备驱动模型展开的。

     

    二、涉及3个重要结构体:

    struct bus_type:总线

    struct device :设备

    struct device_driver:驱动

     

    三、结构体核心代码分析(2.6.38内核)

     

    structbus_type

    {

       const char       *name; // 总线名

       struct bus_attribute   *bus_attrs; //总线属性文件,会显示在/sys/xxx

       struct device_attribute   *dev_attrs;  //设备属性文件

       struct driver_attribute   *drv_attrs;   // 驱动属性文件

       int (*match)(struct device*dev, struct device_driver *drv);//驱动与设备是否匹配的检测函数

       struct subsys_private *p;

    };

     

    其中,struct subsys_private包含一个设备链表(struct klist klist_devices)和一个驱动链表(  struct klist klist_drivers)

     

    structdevice

    {

       struct kobject kobj;

       const char                          //设备名

       struct bus_type   *bus;        // 该设备挂接在哪条总线上

       struct device_driver*driver; //该设备所对应的驱动

       void   *platform_data;    // 平台特定数据,一般我们移植内核时需填充该结构体(如支持mini2440的nandflash,dm9000等)

       dev_t           devt;            // 设备号

     };



     

    任何建立在平台总线设备驱动模型基础上的驱动代码(如平台驱动,PCI驱动,USB驱动,I2C驱动,SPI驱动等),它们的设备结构体(如platform_device, pci_dev,usb_device,i2c_device, spi_device等)都包含一个struct device结构体,当这些驱动向内核注册各式各样的设备时,其实最终都会调用到:

    intdevice_register(struct device *dev)

    {

       device_initialize(dev);  //做各类初始化

       //将设备挂接在对应的总线上,主要工作把设备(device)添加到总线       (bus_type)的klist_devices链表

       return device_add(dev);

    }

     

    structdevice_driver {

       const char       *name;// 驱动名

       struct bus_type       *bus;// 该驱动所属的总线

       int (*probe) (struct device *dev);// 指向设备探测函数

       int (*remove) (struct device*dev);// 指向设备移除函数

       struct driver_private *p;

     };

     

    同理,任何建立在平台总线设备驱动模型基础上的驱动代码(如平台驱动,PCI驱动,USB驱动,I2C驱动,SPI驱动等),它们的驱动结构体(如platform_driver, pci_driver,usb_driver,i2c_driver,spi_driver等)都包含一个structdevice_driver结构体,当这些驱动向内核注册各式各样的驱动时,最终都会调用到:

    int driver_register(structdevice_driver *drv)

    {

        // 将驱动绑定在对应的总线上,主要工作把驱动(device_driver)添加到总线(bus_type)的klist_drivers链表中去

         ret =bus_add_driver(drv);

    }



    无论是调用driver_register()注册驱动, 还是用device_register注册设备, 内核都会调用总线的match函数来探测是否有合适device_driver的device或者是否有合适device的device_driver,如果match成功,则会调用device_driver的probe函数进行更进一步的探测。

     

    这样我们就可以站在一个新的高度上看驱动了

     

    更具体的内容比如怎么探测设置probe请看韦东山2期驱动大全相关视频。

     

    展开全文
  • Linux内核设备模型与总线 - 内核版本 Linux Kernel 2.6.34, 与 Robert.Love的《Linux Kernel Development》(第三版)所讲述的内核版本一样 - 源代码下载路径: ...

    Linux内核设备模型与总线

    -         内核版本 Linux Kernel 2.6.34, 与 Robert.Love的《Linux Kernel Development》(第三版)所讲述的内核版本一样

    -         源代码下载路径: https://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.34.tar.bz2


     

    1.      Linux内核设备模型分析

    1)     kobject对象的设计思想

    -         对于没有接触过JAVA、Python等高级面向对象编程语言工程师,第一次看到struct  kobject对象,都会对它的作用感到困惑,不知道为什么这么多Linux内核对象结构体中,都要有一个看起来没什么用的struct  kobject。

    -         对于接触过JAVA、Python等面向对象编程语言的工程师,对object对象肯定不陌生。在JAVA、Python中,object对象是所有对象的基类,所有的对象最终都会继承到object对象。关于JAVA中object对象及其相关方法的描述,可以参阅JDK在线文档

    -         在Linux内核设备模型中,也是借鉴了JAVA、Python中的做法,让struct kobject对象作为所有内核设备的基类,kobject是一个最高层次的抽象基类,这样Linux内核才能够通过一个继承树体系,管理到系统里的每一个设备。

    -         在Linux内核中,虽然kobject对象可以作为所有设备对象的基类,但是类比于JAVA、Python,我们一般不直接使用kobject这种最高抽象层次的基类作为实际需要开发的设备程序的的直接基类,原因见图1。kobject类比于物质在自然界继承体系里的概念,物质是一个抽象的概念,所有的生物和非生物都继承自物质,即他们和物质都是IS-A的关系。狗 IS-A 物质,水IS-A 物质。但是我们真正研究狗的时候,一般是从其犬科动物或者动物等比较具体的基类开始研究,研究它动物属性,研究它发出叫声的特性,很少研究狗的物质属性(除非是唯物主义哲学家),但是狗确实是物质。

    -         同理,在研究一般具体的Linux设备驱动,如视频设备V4L2驱动的代码,一般都从其上层基类struct  video_device研究起,或者再抽血一些,研究struct  video_device 的基类struct  cdev, 很少直接使用最上层的kobject基类。但是V4L2设备驱动,确实 IS-A kobject。


    Figure 1 自然界继承体系,所有对象都继承自物质

    2)     kobject对象的特性

    -         kobjet对象的声明在与相关的API在include/linux/kobject.h文件中。

    -         kobject 对象有对象引用计数(kref),父子object索引(parent)等基本属性。

    -         kobject提供了sysfs文件系统相关的节点描述,属性与函数,使得Linux系统可以通过特殊的sysfs文件系统,以树形继承的关系来访问Linux内核中的每个具体的kobject对象。实际上,kobject最初的设计目的就是为了在sysfs中模仿windows的设备管理器,提供一个类似树形的,可以管理额访问系统所有设备的接口。

    -         kobject对象还提供了Linux系统设备中hotplug热插拔相关的事件与函数方法,使得内核设备可以支持热插拔机制。

    -         每个继承了object的Linux设备对象,在调用者获得kobject基类对象实例之后,可以通过container_of()函数,一层一层转换,最终获得具体子类对象。有了kobject,就可以实现通过基类来访问子类对象的机制

    3)     Linux内核中继承kobject的主要基础类设备模型

    -         Kobject类似于JAVA中的object类,一般不作为内核设备对象的直接基类。但是类似于JDK中有不少对象直接继承自object对象,JDK中直接继承自object的对象,一般都是最为基础类对象,提供给开发者使用。同样,Linux内核中也有不少继承自kobject的基础类对象,Linux内核驱动开发者可以使用这些直接继承自kobject的基础类设备,来构建实际的Linux设备驱动。

    -         kset是一个集合容器,用于管理一类object子类对象的集合,继承自某个基类kobject的所有基类的kobject对象都可以用一个kset容器来管理。

    -         Linux内核在继承自kobject的重要基础类对象如图2所示。



    Figure 2  Linux内核中直接继承kobject的重要基础类对象

    -         device类对象一般用于Linux各种总线设备(platform虚拟平台总线、USB总线等)的基类,下一节详细介绍。

    -         module对象是用于模块管理接口(就是上一篇文章中单继承与接口一节的接口),接口本身也是一个对象(JAVA中的interface也是对象,继承object),实现了module接口的对象,可以通过模块的方式,动态装载、卸载代码块。

    -         class对象是用于设备分类管理的相关接口,通过class接口可以将内核中各种设备类型的信息导出到用户态。

    -         cdev对象就是典型的字符设备基类对象,所有的字符设备最终都会继承到cdev对象。cdev对象同时制定了字符设备标准的访问接口函数方法。

    -         总之,拥有了以上这些基础类,内核开发者就能开发自己特殊的设备驱动,并且通过这些类和接口,将驱动程序集成到Linux内核中。


    3). Linux内核中是怎么管理维护这些继承类对象

    -         之前的内容讲了一堆面向对象的概念,描述了一堆与Linux设备驱动有关的对象之间的关系,可Linux内核毕竟是C语言写的,内核中如何维护这些基础类对象呢?

    -         在Linux内核drivers/base/ 目录下,有很多重要的代码,目录命名为base/,可见基础类对象这个 名词还是有来源依据的,Linux设备驱动里的对象基本都是继承自drivers/base/里面的对象。

    -         drivers/base/base.h中声明了base的一些私有对象属性,以及API,其中很多API如devices_init(),buses_init(),classes_init(),platform_bus_init()等初始化函数都会在Linux内核init函数的driver_init()中被调用,因此可见,Linux内核在启动时通过base.h中的这些初始化*_init()函数使得Linux内核中整个驱动系统相关的基础类组件对象都能进入工作状态。

    -         查看devices_init()函数实现代码如下,我们发现实际上,内核在初始化devices的时候,使用kobject对象创建了dev_kobj作为所有device子类对象的基类。并且创建了相应的devices_kset来管理这些子类。

    int __init devices_init(void)
    {
    	devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
    	if (!devices_kset)
    		return -ENOMEM;
    	dev_kobj = kobject_create_and_add("dev", NULL);
    	if (!dev_kobj)
    		goto dev_kobj_err;
    	sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
    	if (!sysfs_dev_block_kobj)
    		goto block_kobj_err;
    	sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
    	if (!sysfs_dev_char_kobj)
    		goto char_kobj_err;
    
    	return 0;
    
     char_kobj_err:
    	kobject_put(sysfs_dev_block_kobj);
     block_kobj_err:
    	kobject_put(dev_kobj);
     dev_kobj_err:
    	kset_unregister(devices_kset);
    	return -ENOMEM;
    }



    -         最后,我们实现device的子类对象,并通过device_register()函数将其注册的时候(Linux内核还有很多类似的register函数,例如register_chrdev_region,注册函数意图大同小异,都是让基类能够获取注册后的子类对象),用户就可以通过基类访问注册后的子类对象了。

    -         在device_register()函数中,我们看到注册的device子类会和基类的kset容器以及关联起来,最终系统可以通过基类device对象实例dev_kobj所关联的kset容器来访问deivce的子类对象(一般会通过container_of()函数获取子类对象)。

    -         实际上,Linux内核中,cdev,device等基础类对象,都会在初始化时init()相关的全局变量,Linux内核需要维护这些全局变量以及相关的容器(kset、数组、list都可以看成容器)。

    -         由此可见,面向对象思想里面,继承,多态,虚函数的实现并不神秘,还是通过精巧的设计,用全部变量加容易的方法来管理这些关系。由于有这些由Linux内核维护的全局变量和相关容器,所以在开发设备驱动模块子类时,需要通过注册函数(register)才能让基类能够关联到子类的对象。当有了面向对象的观点,我们可以在更高层次理解Linux内核这些对象的关系,从而设计并改进我们的系统。




    2.      Linux内核总线、设备与驱动

    1). 计算机系统总线模型

    - Linux内核总线的思想来源于如图3所示的计算机系统总线模型。

    - 计算机系统总线控制着外部设备与计算机CPU的通信,任意CPU N都可以通过总线访问到任意外部设备。

    - 一般情况下,外部设备数量都会大于CPU的数量,有了总线,无需为每个外部设备都配备一个CPU。只有外部设备需要CPU来访问读取处理数据,发送控制信号时,一个空闲的CPU才会通过总线控制器与某个外部设备建立通信连接。

    - 一旦CPU处理完某个外部设备的数据之后,CPU可以通过总线控制器,断开和某个外部设备的连接,去处理其它外部设备的访问需求。

    - 总之,计算机系统中的总线模型为数量较多的外部设备提供了一种共享数量较少的CPU的控制访问机制。



    Figure 3 计算机系统中的总线

    2). Linux内核中的与总线

    - Linux内核之后有各种各样的软件总线,系统中所支持的所有总线型驱动设备在/sys/bus/ 目录下可以看到。主要的总线型设备驱动有USB、platform、I2C、SPI、SCSI、mmc等。

    - Linux内核中所有总线接口的的基类以及相关的API都是在include/linux/device.h中声明。其中一个总线接口包括三个核心的基类对象:struct bus_type、struct device 与struct device_driver。这些基类对象与Linux内核中实际的USB、platform、I2C总线接口的继承关系如图4所示。



    Figure 4 Linux内核中,各种总线设备与总线接口基类的对应关系

     

    - Linux 内核总线驱动实际上是模仿计算机系统总线的机制,在一个具体类型的总线上(例如I2C、USB、platform)多数的device设备共享少数的device_driver提供了一种管理机制

    - 通过总线,将一个设备驱动中,逻辑功能部分(device_driver)与硬件具体资源bsp相关的部分(device)分隔开来,使得同一种类型的多数设备实例(device),能够共享同一个驱动程序逻辑代码(device_driver)。

    - struct  bus_type 对象与struct device 对象、structdevice_driver对象构成了一个设备实例化管理接口,对象之间的行为模式如图5所示,类似于抽象共产,将每个device与device_driver装配起来,构造出真正的设备实例。

    - struct bus_type对象在 match()函数方法中,通过对比新发现的device 与 device_driver 的 Id(Id可以是name也可以是dts的描述节点或者实际总线自己定义的匹配Id号都可以匹配),为新加入的device设备找到合适的Id匹配的device_driver,然后调用device_driver的probe()函数方法,进行构造实例化,最终产生实例化的设备驱动并且作为节点挂载在/dev目录下。

    - struct bus_type对象的 remove()函数则处理设备卸载析构的行为

    - bus_type对象与device对象、device_driver对象除了用于实例化的函数方法,还有suspend()、resume()、shutdown()等函数方法,用于实现设备的休眠、唤醒等电源管理相关的功能。

    - struct bus_type对象的uevent()函数方法提供了热插拔事件的相关通信机制,通过该函数接口,可以给用户态发送热插拔相关的异步事件。



    Figure 5 bus_type 与 device、device_driver对象的行为模式

    3).platform总线与设备简介

    - 在Linux 内核中,USB、I2C、SPI等总线都是实际存在的总线,都有对应的相关的外部硬件电路以及相关的标准化通信协议最终以电信号为载体与实际的I2C、USB等实际硬件设备通信。

    - USB、I2C等总线设备,可以通过真正的硬件热插拔,从而触发具体的bus_type总线进行driver与device匹配,最终在/dev/*目录下构造实例化设备驱动。

    - 而struct platform_bus_type对象在Linux内核中代表一个虚拟的平台设备总线。即在系统硬件中,不存在与platform_bus_type对应的硬件电路,在SOC中也不存在对应的总线控制器(USB、I2C等模组在SOC芯片中都有相关的硬件控制器)。

    - 虽然struct platform_bus_type不存在真正的总线,但是我们在处理各种杂七杂八的驱动时(比如LED、智能卡、杂项设备、framebuffer等),也有把device_driver的驱动实现逻辑代码和device硬件bsp相关的代码分离出来的需求, 这样使得同样类型但是占用不同端口或者资源(比如 LED1、 LED2都是LED设备,但是一般会占用不同的GPIO口)的device能够共享同一份device_driver的逻辑代码,不需要为每一个LED设备都写一份驱动(维护量无比巨大)。

    - 因而Linux内核采用 struct platform_bus_type、structplatform_device 与struct platform_driver三个对象继承了总线设备相关的基类对象,模仿系统总线的行为模式,通过struct platform_bus_type来管理 structplatform_driver与struct platform_device的设备匹配与设备构造实例化

    - 虽然platform虚拟平台总线不像usb、I2C等总线接口有真正的硬件设备插拔事件。但是struct platform_driver与struct platform_device对象都实现了module接口,可以编译成module进行insmod/rmmod等动态装载于卸载。那么struct platform_driver与struct platform_device对象在在作为module动态地装载与卸载时,相当于模拟了总线的热插拔事件,那么可以通过insmod/rmmod模拟总线设备的热插拔,来触发struct platform_bus_type对象进行driver与device匹配,并在/dev/*目录下构造实例化真正的设备驱动。

    展开全文
  • Linux内核工程导论——总线:Linux PCI

    千次阅读 2015-08-30 19:42:05
     首先,PCI是一种总线,PCI-e是PCI的升级版,在linux的软件系统中是统一都是driver/pci下。既然是一种总线,物理上就包括总线部分和支持该总线的设备。没有设备PCI总线的存在无意义,没有PCI总线的支持,PCI设备...

    PCI

    PCI系列总线介绍

             首先,PCI是一种总线,PCI-e是PCI的升级版,在linux的软件系统中是统一都是driver/pci下。既然是一种总线,物理上就包括总线部分和支持该总线的设备。没有设备PCI总线的存在无意义,没有PCI总线的支持,PCI设备无法发挥作用(就得使用其他总线)。

             众多设备本身没有从属关系,如果放由他们任意接入系统,就会对可怜的CPU和内存资源产生竞争,因为哪个设备都希望首先获得并且获取最多的系统资源。网络接入里常用的CSDA/DA机制就是为了解决这种问题而存在。但是当网络速度达到了非常高的时候,这种竞争随机退避的算法对时间的浪费就会极大的影响性能。所以一般的高速网络要么是协调的,要么传输是物理上独立的信道。对于计算机系统来说,独立的物理信道由于成本原因不现实,但是一条物理信道上协调通信却是可行的,因为计算机系统的核心就是处理能力。

             这就是总线存在的原因。而总线有很多种,为何各种总线有好有坏呢?也就是要回答各个总线间的区别在哪里?因为总线的本质是调度资源,而调度分配资源的算法各异,所以评价调度资源的效率就是总线的最主要评价手段。但是除此之外,成本、可扩展性、可热插播机制等辅助功能(通常是方便使用的)的支持程度,也是一个总线是否被广泛接受的重要原因。市场是最好的决策者,各个总线在竞争中,很多淘汰了,很多存活了,最耀眼的就是pci,耀眼到以至于linux内核认为几乎所有设备都是挂载到PCI上的,甚至所有外部总线都是挂载pci总线上的。

             上面说的外部总线,例如USB总线的热插播能力,就是外部总线的一种,外部总线的通常特点是相对核心内部总线较慢,但是在可扩展性和热插播能力上做的更好。

    Linux PCI

             首先PCI是总线,所以驱动必然要分为两部分:总线部分和接入总线的设备部分。总线部分描述和实现的就是PCI总线的规范,而接入总线的设备驱动则描述的是接入设备的行为。按照惯例,所有驱动都要注册到PCI总线部分的驱动,以方便总线驱动完成枚举、发现、省电、错误处理等统一的总线操作,并且以总线统一的认知方式提供设备的信息,例如商家、设备类型等。

             需要明白一个模型,就是驱动并不等于设备。驱动是存在于代码的软件模块,而设备是物理存在的。但是驱动是为设备服务的。当设备插入的时候(或之前)驱动已经存在于系统,否则设备不可识别。当设备移除的时候,驱动也不会消失(可以稍后卸载)。通过驱动早于设备识别启动,晚于设备移除而移除内存(可以不移除)。因为无论是设备的添加还是移除都需要驱动里对应的函数被执行。PCI设备也不例外,任何一种PCI设备在插入时,代表该种设备的驱动的probe函数执行,移除时,驱动的退出函数执行。一个驱动对应的是一种设备,一个设备通常对应一种驱动(但复合的设备可能对应多种)。

    画个图

             也正是由于以上的原因,一个PCI设备由于可能内涵多种设备,该种复合PCI设备的驱动就有理由不使用总线的发现注册流程,而是自己去扫描发现属于自己能够驱动的设备。当然自己扫描也是利用pci总线的数据接口与未知设备通信。由于通信方式是统一的,如果对象设备是驱动所希望的,就会回复所期望的回复,而其他设备则不认识或回复不正确。也就是说这两种驱动的设备发现逻辑,一种是总线驱动调度的,一种是设备驱动调度的。然而却都是调用总线的传输接口。

    画个图(两调度一数据)

    PCI设备的初始化

             上面讲述了PCI设备的发现,发现后要进行初始化。要时刻铭记的一点是初始化是由物理设备驱动和总线驱动的软件代码实现的,而不是物理设备本身。

             那么这里就涉及到一个驱动代码和总线代码的交互问题。驱动代码想要做具体的事情都是要通过调用总线代码,而驱动代码决定如何去调用。这就相当于编程时使用一个库中函数。是一个调用关系。然而不同的是,这里的总线代码也有自己的逻辑。也就是可以看出是两个独立的线程实体。总线驱动和设备驱动同时运行,然而设备驱动依赖于总线驱动而正常工作。

           PCI地址空间

             PCI总线协议不仅规定了总线驱动的功能,还规定了想要在pci设备上通信的设备所需要具备的物理条件,任何一个宣称支持pci的设备都必须符合pci协议的规定。其中最重要的就是地址空间。

             地址空间是一个会出现在每个计算机系统的词语。因为所有的处理器对外只看得见地址和地址里面存储的内容。内存数据,设备控制寄存器,设备缓存,甚至磁盘数据等都是通过将自己映射到处理器可见的地址空间中才得以被处理器发现并使用。PCI作为一个高速总线,其总线本身的的寄存器就位于CPU的地址空间内。对于特定的CPU,以x86为例,CPU能看见两个地址空间:内存空间和IO空间,PCI规定,在x86结构下,PCI的放入入口位于IO空间。但是IO空间资源非常有限,所以PCI只占用了两个地址(共8字节)。

             由于PCI总线只占用了CPU的8个字节(两个地址字)的空间,而PCI的功能又很复杂,8个字节又得读又得写,8个字节够吗?答案是够的。PCI精细的将这64个位分解为特定的域,通过不同的组合访问不同的设备,并且双工的将两个地址字分为一个地址(CONFIG_ADDRESS)一个数据(CONFIG_DATA)。例如地址字有的域代表总线编号,有的代表设备编号,有的代表功能编号,有的代表前面域定位到的设备内部的寄存器的编号。这样前面刚定义的编号唯一索引到的设备内部的寄存器通过CONFIG_DATA暴露出来就可以读取和写入了。

    PCI设备配置空间

             上一段说了寄存器编号,这是每个PCI设备都要提供的寄存器。PCI规定了PCI设备所需要提供的寄存器的数目和功能,如此上层的PCI总线就可以对任何种类的设备设置已知的寄存器编号获取和设置该设备的特定功能和信息。这些被PCI协议预定义的每个设备都必须实现的寄存器叫做PCI配置空间。PCI规定该空间总长度为256个字节,一共64个字节。因此写入CONFIG_ADDRESS的寄存器编号的取值范围是0-63。

             这64个配置空间寄存器也被PCI协议预定义。那么着64个寄存器都分别有和定义呢?如果全部列举就显得啰嗦,可以查阅。最重要的有两种:一个是描述设备信息的Device ID和Vendor ID(就是设备号和厂商号,vendor ID需要向PCI协会申请,你懂的,外国定义的标准都会留下这种空间让你申请,说白了就是收钱)。另外一个最重要的就是地址指针。

    设备配置空间的访问方法

             刚说过可以通过寄存器直接访问,但是这是x86的访问方式,在mips或arm访问方式又是另有规定的。由于在不同的平台不同,所以linux就有了抽象的接口(甚至可以通过bios编程接口访问)。但是linux的抽象接口必定是与linux内部的抽象相关的,例如pci_(read|write)_config_(byte|word|dword) 函数可以直接读取一个设备的配置空间的寄存器,然而其需要提供的参数就是pci_dev *结构体。这是上层编程硬件无关的编程思想。

    PCI设备内存缓存数据空间

             配置空间的地址指针是用来做啥的呢?我们刚说了CPU配置和访问PCI设备信息通过两个IO地址空间字进行,但是PCI是个高速总线,如此快的速度,两个字不可能完成全部的数据交互功能。我们也说了,这两个字是配置和获取信息使用的,并不是数据传输的空间。而数据想要传输,必须使用内存,要让CPU看见,也必须有内存空间的地址。这个地址是每个设备一份的,并且是不同的,因为每个设备都要传输数据,都需要缓存数据。因此定义这个地址的最好位置就是PCI设备配置空间的寄存器中。所以配置空间中就定义了。由于每个设备不可能固定的确定自己所要使用的内存地址(否则会冲突),应该由操作系统动态的根据当前内存的使用情况来动态的分配,因此配置空间的数据地址寄存器应该由操作系统,也就是PCI总线驱动写入。那么这个地址是如何确定的呢?

    动态确定PCI设备的数据地址

             这个主题有些偏技术层面,但是手法很有趣,所以拿出来说一下。这个机制也是PCI总线协议规定的。由于每个PCI设备所需要的内存数据空间不是一样的(换句话说是PCI总线协议允许你不一样),也就是说每个设备不可以要求地址的具体位置,但是必须在配置空间中告诉系统要多大的内存。这两步是通过同一个寄存器完成的(基地址寄存器,32位)。一个设备会将该寄存器的部分为设为可写,部分位设为只读,当系统上电的时候会向全部的位写入1,如此由于部分位不可写,其值就为0,部分位可写,其值就为1。如此操作系统再读取就会得到一个设备相关的值,该值就表示需要的空间大小。比如该设备需要64KB的地址空间,这个值就是0xFFFF0000。系统得到大小后,就分配所需大小的内存并将其分配得到的地址写入该寄存器的可写部分。如此之后PCI设备就知道他所拥有的内存地址空间了。而内核中的其他组件就可以通过CONFIG_ADDRESS和CONFIG_DATA两个寄存器读取该设备的配置空间来获得该设备的内存数据地址了。

             这个地址位于PCI设备硬件上的最大好处就是DMA,由于设备直接可见数据地址,所以可以不经过CPU,直接使用DMA引擎将数据传输到内存。

    初始化流程

             上面描述PCI设备内存基地址的确定是设备初始化的一部分。完整的初始化流程如下:

    1.        使能PCI设备。要使用一个PCI设备必须得经过总线驱动的同意,否则后续操作命令总线驱动不予传达

    2.        初始化PCI设备的内存数据空间、基地址寄存器和DMA

    3.        向中断子系统申请中断号。数据DMA后要通知内核,通知内核要使用中断号来让内核知道是谁

    设备移除的关闭流程正好是相反的。

    PCI驱动职责与PCI总线驱动职责

             前面说过PCI设备驱动与PCI总线驱动如设备驱动依赖总线驱动的两个线程。总线驱动是固定的,设备驱动是要不同的设备商家使用总线驱动来实现的。而PCI总线中规定了很多操作,这些操作并不是每个设备驱动都要实现的,正因为是总线规定的,所以具有通用性,很多就实现在了PCI总线驱动中,所以PCI设备的驱动作者就得明白什么不需要自己实现,什么需要。例如Fast Back to Back writes机制就是实现在总线驱动中的。如设备ID,总线驱动中不可能全部包含,所以各个驱动就需要在总线驱动不包含的情况下自己定义在驱动中。

             另外,虽然总线驱动实现了PCI设备的发现和配置,怎么使用这个配置也是由总线规定的,但是实际的使用者却是驱动。例如PCI总线驱动配置了PCI设备的内存数据的地址,但是设备驱动在向这个地址写入数据的时候必须得了解到这个地址是内存地址,而驱动要写入的数据是要写到设备中的。写入到内存地址后,DMA并不一定立即启动将数据传输到设备,就算启动也需要时间。因此设备驱动在写入内存后如果要读,需要等待一段时间或者强制将数据立即刷到设备上。

    PCI中断系统:MSI

             MessageSignaled Interrupts。PC机的物理系统上,中断是通过引脚的高低电瓶实现的,一般的CPU只有很少的中断输入,为了区别更多的中断,通常外置中断芯片,中断芯片向外提供很多中断引脚,通过查询中断芯片的寄存器获得时哪个设备的中断。先进一些的系统在CPU内部就有分级的中断标识,但所有这些都是通过引脚高低电平变化实现的。内核定位中断类型是通过树形的寄存器组织追踪哪个置位确定的。

             linux内核中抽象了这种硬件树形中断架构,因为不同的芯片树的组织不一样,甚至简单的直接是几个扁平的寄存器就可以代表所有中断,甚至中断类型不需要使用寄存器,而是使用中断函数去查询确定是哪个设备的中断。更有难以的处理的是多个CPU分享组织不同的中断。由于组织的不确定性,但区分不同中断的需求是确定的,linux内核提供了以中断号为核心的中断系统。每个中断对于linux来说都是某个中断号(例如32号中断)有中断,调用与该中断号关联的中断处理程序执行,而一个中断号可以挂载多个中断处理程序,以方便多个驱动共享同样的中断号。

             PCI协议为PCI设备定义了新的中断方式,虽然如此,其上层也是使用的操作系统的固定的中断方式。也就是说任何一个PCI设备发生了中断,PCI总线对应的中断引脚还是会起作用,中断号还是会被激发,对应的中断处理函数还是会被调用。所不同的是,PCI是个总线,对于CPU来说虽然只是一个PCI中断,但实际上可能是总线上任何一个设备的中断信号。PCI协议定义了一种协议来区别是哪个PCI设备的中断,这种协议叫做MSI(或升级版的MSI-X)。

             其原理是任何一个PCI设备发生中断,其向PCI总线的驱动程序发送一个信号消息,该消息代表了具体的中断信息。这种机制会在内存中模拟出类似传统的中断寄存器,只是大小不受限于一个寄存器的大小。因此MSI可以支持32个中断源,MSI-X可以支持2048个中断源。MSI的32个中断源在内存中必须连续分配,而MSI-X不需要。MSI的可能受限于单个CPU,而MSI-X可以跨CPU分配。可以看出MSI-X是对MSI的升级。

             MSI的存在,使得触发中断的时机可以由设备掌控(例如让数据传送完毕再触发中断),并且可以让一个PCI设备可以触发多种中断(这在传统架构中是不可以的)。如果没有这种机制,处理中断的步骤将会是PCI总线的中断引脚被触发,CPU只会知道是PCI总线上的某一个设备发生了中断,然后调用中断处理程序,遍历PCI总线上的所有设备,直到找到发出中断的设备。由于PCI速度快,发生中断很可能是并发的,如此的响应方式就会产生很多问题(例如饥饿)。

             当然linux允许你关闭PCI的MSI功能(当然PCI协议也是允许的),关闭之后就采用传统的中断处理方式去遍历寻找。显著降低效率,但是考虑到不是所有的PCI设备都支持MSI,所以这种支持是有必要的。

    PCI-E错误处理

             一个总线协议标准一定会定义错误处理与恢复,相对应的一个总线的软件驱动也必须实现这种错误处理机制。在linux中这个机制实现的名字叫PCI Express Advanced Error

    Reporting (AER) driver。

             PCI-E定义了两种错误处理方式:一种是所有设备都支持的baseline capability,提供最小支持,但是要求所有PCI设备都支持可以汇报。另一种是扩展的AER机制,其实就是提供更多的错误信息,方便前端用户调试和恢复。AER驱动就是收集这种扩展信息,并且提供给用户的机制。

    PCI设备用户空间视图

             用户可以用lspci来查看当前的PCI设备。

             每一行的开头有3个数,第一个是总线编号,这里都是00(一个系统可以有多条总线,每个总线都是一个单独的域,叫做PCI域)。第二个是设备编号,可以唯一的定位一个设备。第三个是功能编号,一个设备可以有多个功能。        

    sys目录下也有。

    PCI总线协议

             物理上的东西,王齐的《PCIExpress体系结构导读》中说的比较详细。

             总的来说PCI是一种传统的总线结构,PCI-E则变成了目前高速设备正在普遍采用的网络结构。通常的总线结构相当于计算机网络的总线结构,所有设备共享总线,通过总线调度为各个设备提供服务。而网络结构相当于计算机网络的星型结构,系统中各个设备直接连接到交换机,交换机连接到路由器,各个路由器之间通过路由进行转发通信。现代计算机网络思想已经全面进入硬件内部领域。

             PCI-E由于是星型网络的。每个小型的PCI-E系统中只有一个RC(root complex,直接连接CPU的设备,相当于路由器),实际的架构就需要交换机设备,叫做switch。

    画图:PCI_E星型拓扑

             每个switch可以有多个口,switch之间可以级联。所以必然有一个switch直接连接到RC,然后向下扩展成网络,每个switch的出口都可以接设备,并且只能接入一个。这在我们的交换机网络中是司空见惯的事情,但是在硬件中这却是一个不小的进步。因为之前的做法相当于每个switch口出来可以一条总线挂载多个设备。

             星型的网络结构显著的增加了吞吐量和链路利用效率和可扩展性,USB总线等现代总线都已经采用这种结构。USB总线的流行的根本原因可能也是因为这个精彩的架构的使用。

    物理层

             PCI-E的物理层抛弃了PCI的单端信号(使用不稳定的地作为信号回路),改用了抗干扰能力极强的差分信号。采用的编码是8/10编码。8/10编码是IBM已经过期的专利编码,这个编码专门用在告诉串行总线。由于高速串行总线要求电流总体为0(直流平衡),所以数据流中的1和0的数目必须一样多。所以必须经过编码,能够让0和1一样多的编码就是8/10编码。

             如果连续的出现1或0也会导致物理链路出现问题(耦合电容充满),所以8/10编码还保证连续1或0不超过5个(这是标准的,PCI-E使用的8/10编码可以保证10个位中最多有6个1或0,从而大致的保证不连续)。编码的时候将8个字节分为3和5个,然后编码为4和6个。但是大致的保证并不能一定,所以PCI-E还有额外的机制防止万一叫CRD,这里就不多介绍。

    协议数据包

             既然PCI-E采用了交换式架构,这也决定了其使用的可交换的数据包进行通信。协议定义了很多数据包,分为两层:数据链路层和事务层。链路层有链路训练的功能,总线事务也有很多种,例如存储器读写、配置读写、Message总线事务、原子操作。还有一些高级功能,如流量控制、虚通路管理等。两层都有数据数据报文和控制报文两种。下层为上层提供服务,上层使用下层的服务接口。

    PCI总线与USB总线的关系

             你使用lspci命令列出所有的pci设备,你会发现在linux内核看来,大部分情况下usbcontroller是pci-e的一个设备,也就是说usb总线是挂载在pci(pci-e)总线上的。因此到usb的存储数据的真实流向应该是:用户——文件系统——通用块层——SCSI——USB——PCI。大家可能会疑惑的一点是从cpu出来数据明明是首先经过pci-e,然后再经过usb controller到达usb设备,为何内核中数据走向是先经过usb?一个更完整的单机流程如下:

    用户——文件系统——通用块层——SCSI驱动——USB驱动——PCI驱动——PCI硬件——USB硬件——USB设备。还是强调一点:驱动和硬件不是一个实体。


    展开全文
  • 现实的linux设备和驱动通常都需要挂接在一种总线上,比较常见的总线有USB、PCI总线等。但是,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。基于这样的背景下,...
  • Platform总线  PCI总线只是一种USB挂载的总线选择。...当USB直接连接到芯片,或者连接到其他总线时,Linux认为所有非PCI总线的设备都位于platform总线上。这个总线linux虚拟的,用于同一管理。  对L
  • 引言本文介绍linux内核是如何支持总线设备驱动框架的。因为amba相关的代码结构比较简单,代码目录位于drivers/amba/下,源代码只有两个文件,分别为bus.c和tegra-ahb.c,相关头文件include/linux/amba/bus.h。我们...
  • linux内核相关视频解析: 5个方面分析linux内核架构,让你对内核不再陌生 90分钟了解Linux内存架构,numa的优势,slab的实现,vmalloc的原理 手把手带你实现一个Linux内核文件系统 简介 作用是将应用层序的请求传递...
  • Linux内核开发_1_编译LInux内核

    千次阅读 2020-05-13 01:23:32
    Linux内核版本: cat /proc/version Linux version 4.15.0-20-generic (buildd@lgw01-amd64-039)\ (gcc version 7.3.0 (Ubuntu 7.3.0-16ubuntu3)) #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018 ...
  • Linux内核设备、驱动和总线的概念

    千次阅读 2011-04-27 10:40:00
    Linux2.6内核设备模型中有三个重要概念:设备,驱动和总线。其中设备是设备,总线总线,驱动是驱动,各负其责,严格区分,有各自的内核结构定义。Ø 设备通过struct device结构体定义,但通常将该结构体嵌入到...
  • 写给Linux内核新手-关于Linux内核学习的误区 写给Linux内核新手-关于Linux内核学习的误区 先说句正经的:其实我没资格写这篇文章,因为自己也就一两个月以来才开始有所领悟的。因此,这里与其说是关于Linux内核...
  • /sys/devices/platform/hisi_i2c.2/i2c-2# ls 2-0070/ -l lrwxrwxrwx 1 root root 0 Jan 1 00:06 driver -> ../../../../../bus/i2c/drivers/ad-7441 -r--r--r-- 1 root root 4096 Jan 1 00:06 modalias ...
  • mmc总线使用实例 broken-cd 表示没有热插拔探测引脚,使用轮询检测 cd-gpios 使用gpio管脚作为热插拔探测引脚 non-removable 表示不能进行热插拔,设备一直连接(比如eMMC) 上面三个选项用于指定热插拔探测选项,...
  • CentOS 7 升级 Linux 内核

    万次阅读 多人点赞 2018-02-28 16:52:37
    升级 CentOS 内核参考资料 1 ...Linux 内核分两种:官方内核(通常是内核开发人员用)和各大 Linux 发行版内核(一般用户常用)。 1.1 官方内核 在使用 Docker 时,发现其对 Linux 内核版本的最低要求...
  • 一张图看懂Linux内核的“总线-设备-驱动”架构中的设备、驱动函数调用:
  • linux内核   linux内核版本号格式   major.minor.patch-build.desc  1、major:表示主版本号,有结构性变化时才变更。  2、minor:表示次版本号,新增功能时才发生变化;一般奇数表示测试版,偶数表示...
  • §1.PCI总线体系结构概述PCI总线体系结构是一种层次式的(Hierarchical)体系结构。在这种层次式体系结构中,PCI桥设备占据着重要的地位,他将父总线和子总线连接在一起,从而使整个系统看起来像一颗倒置的树型结构...
  • pianogirl 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000一、Linux内核源代码http://codelab.shiyanlou.com/xref/linux-3.18.6/值得关注的几个目录:/arch- ...
  • linux内核注册总线例子

    千次阅读 2013-05-17 11:57:28
    #include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("Driver Monkey"); static int ...
  • Linux USB总线架构

    千次阅读 2017-08-07 12:21:49
    Linux USB总线架构

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 51,714
精华内容 20,685
关键字:

linux内核总线

linux 订阅