• linux bus总线


     
    linux  bus
    谨以此文纪念过往的岁月。
    在linux中bus很重要,主要是bus连接了devices和drivers,devices和drivers通过bus来进行一对一的匹配。
    记录学习bus的过程,如有错误请指正。
    1.bus的注册以及bus属性文件的创建
    注册一个bus一如注册一个设备和驱动一样,在内核中调用API就可以,如果仅仅满足于知道怎么去调用API则不是一个好的驱动工程师。
    在 注册bus时,注意一个区别xx_bus和xx_bus_type,对于这两个区别,在看其结构时就应该分清楚,xx_bus是设备类 型,xx_bus_type是总行类型,xx_bus作为以后其隶属于该bus的设备的父设备,而xx_bus_type则是描述给总线类型。打个不恰当 的例子,一个是真正的设备,一个是用于描述该设备的。对于一个简单的bus驱动,需要定义xx_bus_type,对于该结构体中的函数,有些需要实现有 些则不需要,根据不同的总线,实现不同的函数。如:
    struct bus_type ldd_bus_type = {
     .name = "ldd",
     .match = ldd_match,
    };
    上面的ldd_bus_type只实现了一个驱动和设备匹配的函数ldd_match。
    其中struct bus_type 的定义如下:
    struct bus_type {
     const char  *name;       --总线名
     struct bus_attribute *bus_attrs;    --总线属性
     struct device_attribute *dev_attrs;  --总线设备属性
     struct driver_attribute *drv_attrs;  --总线驱动属性
      以下的函数会在设备注册或驱动注册的时候调用。
     int (*match)(struct device *dev, struct device_driver *drv);      --实现设备与驱动的匹配。不同的总线实现匹配的方法不同,如platform总线采用name匹配,而usb_bus采用id匹配
     int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
     int (*probe)(struct device *dev);                           --在2.6的内核中实现一个设备与驱动的探测。主要是因为热插拔的设备的增多。
     int (*remove)(struct device *dev);                          --移除设备
     void (*shutdown)(struct device *dev);                       --关闭设备
     int (*suspend)(struct device *dev, pm_message_t state);     
     int (*suspend_late)(struct device *dev, pm_message_t state); 
     int (*resume_early)(struct device *dev);                     
     int (*resume)(struct device *dev);
     
     struct dev_pm_ops *pm;                                   --电源管理
     struct bus_type_private *p;                            --bus_type私有成员,这个结构体中主要包括了kset以及klist,用于管理其挂载其总线下的设备和驱动
    };
    struct bus_type_private {
     struct kset subsys;
     struct kset *drivers_kset;
     struct kset *devices_kset;
     struct klist klist_devices;
     struct klist klist_drivers;
     struct blocking_notifier_head bus_notifier;
     unsigned int drivers_autoprobe:1;
     struct bus_type *bus;
    };
    上面两个结构体很重要,这个涉及到真正的设备和驱动管理。
    内核中采用bus_register来注册一个新的总线。其使用如:ret = bus_register(&ldd_bus_type);
    bus_register函数的源码如下(剔除一些错误处理):
    int bus_register(struct bus_type *bus)
    {
     int retval;
     struct bus_type_private *priv;
     priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
     priv->bus = bus;
     bus->p = priv;
     BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
     retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);   --设置kobject名称,有时会使用snprintf来设置,不过会出现bug,最好使用该函数。
     priv->subsys.kobj.kset = bus_kset;  --kobj中的kset指向父kset
     priv->subsys.kobj.ktype = &bus_ktype;  --kobj中的ktype指向父ktype
     priv->drivers_autoprobe = 1;          --设置总线下的设备和驱动自动探测
     retval = kset_register(&priv->subsys);    --注册kset,关于kset,kobject,以及ktype的关系以后在学习
     retval = bus_create_file(bus, &bus_attr_uevent);   --创建bus的文件属性
      
     priv->devices_kset = kset_create_and_add("devices", NULL,&priv->subsys.kobj); --在父kset下创建和加入名为devices和drivers的kset
     priv->drivers_kset = kset_create_and_add("drivers", NULL,&priv->subsys.kobj);
     klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);   --初始化klist,这个函数主要是实现一些赋值及初始化的功能
     klist_init(&priv->klist_drivers, NULL, NULL);
     retval = add_probe_files(bus);   --主要是创建该bus下probe的属性文件,可以通过cat file 和cat x > file 向属性文件读出和写入数据。
     retval = bus_add_attrs(bus);     --添加bus自带的属性文件
     return 0;
    }
    下面函数其实是实现一个从链表的节点,查询到该节点所属的设备,并且减少dev的计数。
    static void klist_devices_get(struct klist_node *n)
    {
     struct device *dev = container_of(n, struct device, knode_bus);
     get_device(dev);
    }
    其实bus的注册比较简单,说到底还是kset和kobject控制着这一切,抛开kset和kobject的具体实现,bus还是很好理解的。
    如果说bus注册完后,仍然想为bus添加其他的属性文件,则可以通过bus_create_file来创建该bus的属性文件。
    例如:
    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);
    通过BUS_ATTR宏定义定义一个属性文件,其参数依次是属性文件名,属性文件的mode,显示属性文件函数和存储属性文件函数。然后通过下面的函数调用就可以了,下面的函数其实是通过调用sysfs_create_file来实现的。
    bus_create_file(&ldd_bus_type, &bus_attr_version);
    到此bus_type算是注册了,单有bus类型还是不够得,还要注册一个真正的bus设备。
    亦如注册一个简单设备一样,定义一个设备,然后在注册就可以了,
    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
    };
    上面是一个定义,然后调用device_register(&ldd_bus)就可以了。
    以上就是一个总线的注册。不过在实现上面的总线注册后,还有实现隶属于该总线的设备和驱动是如何注册和卸载的。
    如该总线下的设备如何注册:
    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);
    }
    void unregister_ldd_device(struct ldd_device *ldddev)
    {
     device_unregister(&ldddev->dev);
    }
    EXPORT_SYMBOL(register_ldd_device);
    EXPORT_SYMBOL(unregister_ldd_device);
    而驱动的注册如下:
    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);
    }
    void unregister_ldd_driver(struct ldd_driver *driver)
    {
     driver_unregister(&driver->driver);
    }
    EXPORT_SYMBOL(register_ldd_driver);
    EXPORT_SYMBOL(unregister_ldd_driver);
    驱动和设备结构体如下:
    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);
    struct ldd_device {
     char *name;
     struct ldd_driver *driver;
     struct device dev;
    };
    不过像这种总线下的设备以及驱动的注册以及设备及驱动的结构体,都可以自己想怎么整就怎么整,不过有有些函数是必须调用的如:
    device_register 和driver_register以及他们的反函数。这几个函数才是设备驱动的真正核心,其余的只不过是在这两个函数之上的一些增加而已。以 platform总线为例platform设备注册调用platform_device_register,驱动注册调用 platform_driver_register最终都会调用device_register和driver_register。
    以上即是一个简单的总线注册,在linux内核中,总线无处不在,他将设备与驱动连接,其实不管总线的复杂与否,其根本的本质脱离不了其上的模式。如果理解以上的总线,在去理解usb_bus,platform_bus都会简单不少。透过那层层迷雾,会发现他其实很简单。
  • 相关阅读:
    docker cacti
    zabbix5.0官方部署+监控nginx+mysql
    CentOS7 Haproxy2.2.2部署示例
    LVS(DR) + keepalived
    linux备份整个系统
    docker部署OceanBase 试用版
    NextCloud开源视频会议平台
    idea使用maven proguard 对ssm项目进行代码混合详细步骤
    C# 范围运算符[1..2]
    对象是否为空的扩展方法
  • 原文地址:https://www.cnblogs.com/wangxianzhen/p/3009752.html
Copyright © 2020-2023  润新知