总线,设备,驱动与class(一)

简介: 总线,设备,驱动与class

前面己经介绍了Linux设备驱动模型的底层数据结构及相关操作,现在开始讨论该模型的高层部分,也是Linux下设备驱动程序员与之打交道最多的部分。高层部分的核心分为三个组件,正如本节标题揭示的那样,分别是总线(bus)、设备〈device)和驱动(driver),它们构成了Linux设备驱动模型这一宏大建筑的外在表现。接下来将依次讨论每个组件,看看Linux引入的这个新的设备模型到底给系统,给设备驱动程序员带来了哪些好处和不足。

总线

总线及其注册

总线可以看成Linux设备驱动模型这座建筑的核心框架,系统中其他的设备与驱动将紧密团结在以总线为核心的设备模型的周围,完成各自的使命。不过设备驱动程序员在系统中创建一个新的总线的机会并不多。驱动模型中的总线,既可以是实际物理总线(比如PCI总线和I2C总线等)的抽象,也可以是出于驱动模型架构需要而产生的虚拟“平台”总线,因为一个符合Linux驱动模型的设备与驱动必须挂靠在一根总线上,无论它是实际存在的总线还是系统虚拟出的总线。

内核为总线对象定义的数据结构是bus_type,其完整定义如下:

//总线对象,描述一个总线,管理device和driver,完成匹配.总线数据结构类型的表示,成员:
struct bus_type {
  const char    *name;//总线的名称。如同每个人都有一个名字一样,总线也不例外。
  const char    *dev_name;
  struct device   *dev_root;
  struct device_attribute *dev_attrs; //挂载到该总线上的设备的属性,功能逻辑与总线属性一样。
  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);
  //匹配总线中dev和driver,返回1 匹配成功,否则匹配失败
  int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
  //用于总线对uevent的环境变量增加,但在总线下设备的dev_uevent处理函数也有对他的调用
  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;
};

subsys_private 内核中定义为:

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;

其中,struct kset subsys用来表示该bus所在的子系统,在内核中所有通过busregister注册进系统的bus所在的kset都将指向bus kset,换句话说buskset是系统中所有bus内核对象的容器,而新注册的bus本身也是一个kset型对象。struct kset *driver_skset表示该bus上所有驱动的一个集合,struct kset *devices_kset则表不该bus上所有设备的一个集合。struct klist klist_devices和struct klist klist_drivers则分别表示该bus上所有设备与驱动的链表。drivers_autoprobe用来表示当向系统(确切地说是系统中某一总线)中注册某一设备或者驱动的时候,是否进行设备与驱动的绑定操作。struct bus_type *bus指向与struct bus_type_private对象相关联的bus。

为了便于接下来对总线、设备与驱动的讨论,我们从总线的角度,给出图所示三者之间的互联层次关系:

图9·3展示了一个总线对象所衍生出来的拓扑关系,这种拓扑关系主要通过bus_type中的struct *p成员来体现。在这个成员中,struct kset_subsys标识了系统中当前总线对象与bus kset间的隶属关系,而struct kset *drivers_kset和struct kset *devices_kset则是在向系统注册当前新总线时动态生成的容纳该总线上所有驱动与设备的kset,与此对应,两个klist成员则以链表的形式将该总线上所有的驱动与设备链接到了一起。

Linux内核中针对总线的一些主要操作有:

  • buses_init
    buses_init函数揭示了总线在系统中的起源,在系统的初始化阶段,就通过buses_init函数为系统中后续的bus操作奠定了基础,该函数的实现为:
int __init buses_init(void)
{
  bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
  if (!bus_kset)
    return -ENOMEM;
  system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
  if (!system_kset)
    return -ENOMEM;
  return 0;
}

它将创建一个名称为“bus”的kset并将其加入到sysfs文件系统树中,注意这里的bus_uevent_ops定义了当“bus”这个kset中有状态变化时,用来通知用户空间uevent消息的操作集。前面在讨论kset时知道,当某个kset中有状态的变化时,如果需要向用户空间发送event消息,将由该kset的最顶层kset来执行,因为buskset是系统中所有bus subsystem最顶层的kset,所以bus中的uevent调用最终会汇集到这里的bus_uevent_ops中。这个操作集只定义了一个filter操作,意味着当“bus,中发生状态变化时,会通过bus_uevent_ops中的filter函数先行处理,以决定是否通知用户态空间bus_uevent_ops定义如下:

static int bus_uevent_filter(struct kset *kset, struct kobject *kobj)
{
  struct kobj_type *ktype = get_ktype(kobj);
  if (ktype == &bus_ktype)
    return 1;
  return 0;
}
static const struct kset_uevent_ops bus_uevent_ops = {
  .filter = bus_uevent_filter,
};

如果要求发送uevent消息的kobj对象类型不是总线类型(bus_type),那么函数将返回0,意味着uevent消息将不会发送到用户空间,所以bus_uevent_ops使得bus_kset只用来发送bus类型的内核对象产生的uevent消息。

buses_int将在sysfs文件系统的根目录下建立一个“bus”目录,在用户空间看来,就是/sys/bus。buses_init函数创建的“bus”总线将是系统中所有后续注册总线的祖先。

  • bus_register
    该函数用来向系统中注册一个bus,其部分核心代码如下:
//注册总线
 /*在可以注册设备及驱动程序之前,需要有总线,提供此函数注册总线*/
int bus_register(struct bus_type *bus)
{
  int retval;
  struct subsys_private *priv;
  struct lock_class_key *key = &bus->lock_key;
  priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
  if (!priv)
    return -ENOMEM;
  priv->bus = bus;
  bus->p = priv;
  BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
  retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
  if (retval)
    goto out;
  priv->subsys.kobj.kset = bus_kset;
  priv->subsys.kobj.ktype = &bus_ktype;
  priv->drivers_autoprobe = 1;
  retval = kset_register(&priv->subsys);//在/sys/bus目录下为当前注册的bus生成一个新的目录
  if (retval)
    goto out;
  retval = bus_create_file(bus, &bus_attr_uevent);//生成bus的属性文件
  if (retval)
    goto bus_uevent_fail;
  priv->devices_kset = kset_create_and_add("devices", NULL,
             &priv->subsys.kobj);//为当前bus产生容纳设备的kset容器
  if (!priv->devices_kset) {
    retval = -ENOMEM;
    goto bus_devices_fail;
  }
  priv->drivers_kset = kset_create_and_add("drivers", NULL,
             &priv->subsys.kobj);//为当前bus产生容纳驱动的kset容器
  if (!priv->drivers_kset) {
    retval = -ENOMEM;
    goto bus_drivers_fail;
  }
  INIT_LIST_HEAD(&priv->interfaces);
  __mutex_init(&priv->mutex, "subsys mutex", key);
  klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);//初始化bus上的设备与驱动的链表
  klist_init(&priv->klist_drivers, NULL, NULL);
  retval = add_probe_files(bus);//为当前bus增加probe相关的属性文件
  if (retval)
    goto bus_probe_files_fail;
  retval = bus_add_groups(bus, bus->bus_groups);
  if (retval)
    goto bus_groups_fail;
  pr_debug("bus: '%s': registered\n", bus->name);
  return 0;
bus_groups_fail:
  remove_probe_files(bus);
bus_probe_files_fail:
  kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
  kset_unregister(bus->p->devices_kset);
bus_devices_fail:
  bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
  kset_unregister(&bus->p->subsys);
out:
  kfree(bus->p);
  bus->p = NULL;
  return retval;
}

函数首先分配一个struct subsys_private类型的对象,然后通过kobject_set_name为bus所在的内核对象设定名称,该名称将显示在sysfs文件系统树中。前面提到,bus作为一个kset类型的内核对象,其对象属性等特性体现在对象的subsys成员中,这是个kset型变量,所以注册一个bus,将同时赋予该bus特定的属性特质,这由下面两条语句完成:

priv->subsys.kobj.kset = bus_kset;
  priv->subsys.kobj.ktype = &bus_ktype;

第一条语句指明了当前注册的bus对象所属的上层kset对象,就是buses_init中创建的名为“bus”的kset。第二条语句指明了当前注册的bus的属性类型bus_ktype,后者定义了该特定bus上的一些与总线属性文件相关的操作:

static struct kobj_type bus_ktype = {
  .sysfs_ops  = &bus_sysfs_ops,
  .release  = bus_release,
};

bus_sysfs_ops中的操作主要是用来显示(show)或者设置(store)当前注册的bus在sysfs文件系统中的属性。

函数中的kset_register(&priv->subsys)用来将当前操作的bus所对应的kset加入到sysfs文件系统树中,因为priv->subsys.kobJ.parent=NULL并且priv->subsys.kobJ.kset=buskset,所以当前注册的bus对应的kset的目录将建立在/syus当中。

bus_create_file(bus,&bus_attr_uevent)将为该bus创建一属性文件。关于bus的属性问题,稍后将另开一节予以讨论。

接下来可以看到有两个kset_create_and_add调用:

priv->devices_kset = kset_create_and_add("devices", NULL,
             &priv->subsys.kobj);//为当前bus产生容纳设备的kset容器
  if (!priv->devices_kset) {
    retval = -ENOMEM;
    goto bus_devices_fail;
  }
  priv->drivers_kset = kset_create_and_add("drivers", NULL,
             &priv->subsys.kobj);//为当前bus产生容纳驱动的kset容器
  if (!priv->drivers_kset) {
    retval = -ENOMEM;
    goto bus_drivers_fail;
  }

前面讨论过kset_create_and_add函数,它将生成一个kset对象并将其加入到sysfs文件系统中。注意这里在调用kset_create_and_add函数时,parent参数均为&priv->subsys.kobJ,这意味着将在当前正在向系统注册的新bus目录下产生两个kset目录,分别对应新bus的devices和drivers,假设新bus的名称是“newbus”,那么反应到/sys文件目录中就是

/sys/bus/newbus/devices和/sys/bus/newbus/drivers。

图9-4反应了通过bus_register向系统注册一个新的busl时所产生的组件及层次关系:

图中虚线部分是将busl通过busregister注册进系统时所产生的层次关系结构。首先代表bus1的一个kset对象将被产生出来并且血入到sysfs文件系统中,该kset的parent内核对象为buses_init函数中所产生的bus_kset。其次bus_regster通过调用kset_create_and_add

函数产生连接到bus1上的devices_kset和drivers_kset两个集合,对应到sysfs文件系统,将会在bus1的目录下产生两个新的目录"devices”和"drivers”。最后为了让用户空间看到或者重新配置bus1上的某些属性值,bus_register调用bus_createf_ile函数为bus1产生一些属性文件,这些属性文件也将位于/syu1目录之下,属性文件实际上向用户空间提供了一种接口,使得用户程序可以通过文件的方式来显示某一内核对象的属性或者重新配置这一属性。

总线的属性

总线属性代表着该总线特有的信息与配置,如果通过sysfs文件系统为总线生成属性文件,那么用户空间的程序可以通过该文件接口的方式很容易地显示或者更改该总线的属性。根据实际需要,可以为总线创建不止一个属性文件,每个文件代表该总线的一个或一组属性信息。总线属性在内核中的数据结构为:

struct bus_attribute {
  struct attribute  attr;
  ssize_t (*show)(struct bus_type *bus, char *buf);
  ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};

成员变量attr表示总线的属性信息,其类型为struct attribute:

struct attribute {
  const char    *name;
  umode_t     mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
  bool      ignore_lockdep:1;
  struct lock_class_key *key;
  struct lock_class_key skey;
#endif
};

struct bus_attribute的另外两个成员show与store分别用来显示和史改总线的属性。内核定义有一个宏BUSATTR,用来方便为总线定义一个属性对象:

#define BUS_ATTR(_name, _mode, _show, _store) \
  struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define __ATTR(_name, _mode, _show, _store) {       \
  .attr = {.name = __stringify(_name),        \
     .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },   \
  .show = _show,            \
  .store  = _store,           \
}

BUS_ATTR宏将定义一个以“bus_attr_”开头的总线属性对象,而生成总线属性文件则需要使用bus_create_file函数:

int bus_create_file(struct bus_type *bus, struct bus_attribute *attr)
{
  int error;
  if (bus_get(bus)) {
    error = sysfs_create_file(&bus->p->subsys.kobj, &attr->attr);
    bus_put(bus);
  } else
    error = -EINVAL;
  return error;
}

sysfs_create_file用来在sysfs文件树中创建一个属性文件,这里不会讨论sysfs实现这个函数的细节。我们关注的是,用户层的应用程序如何利用总线属性文件的接口来显示和更改总线属性。这里以bus_register函数中的add_probe_files调用为例,后者会用BUS_ATTR宏定义一个总线属性,然后为之生成一个属性文件(读者可以在/sys/bus目录中的任一总线目录下发现drivers_autoprobe文件)。

通过BUSAI丁R宏,add-probefiles为此定义的总线属性为:

static BUS_ATTR(drivers_probe, S_IWUSR, NULL, store_drivers_probe);
static BUS_ATTR(drivers_autoprobe, S_IWUSR | S_IRUGO,
    show_drivers_autoprobe, store_drivers_autoprobe);

上面的宏将产生一个总线属性对象bus_attr_drivers_autoprobe,该文件的模式为S_IWUSR |S_IRUGO,表明对root用户而言具有读与写的权限。

显示该总线属性的函数为show_drivers_autoprobe:

static ssize_t show_drivers_autoprobe(struct bus_type *bus, char *buf)
{
  return sprintf(buf, "%d\n", bus->p->drivers_autoprobe);
}
static ssize_t store_drivers_autoprobe(struct bus_type *bus,
               const char *buf, size_t count)
{
  if (buf[0] == '0')
    bus->p->drivers_autoprobe = 0;
  else
    bus->p->drivers_autoprobe = 1;
  return count;
}

通过上面这个函数实现,可以发现该属性文件向用户空间提供了一个显示和更改bus->p->drivers_autoprobe成员的接口。

在构造sysfs文件系统的超级块时,内核会调用到kernfs_init_inode函数,这个函数为sysfs文件系统中的inode初始化了相关的操作对象i_op和i_fop,这样对于在sysfs文件系统中生成总线属性文件的bus_create_file而言,它生成的属性文件被用户空间的shell命令操作时,将利用到inode上i_fop操作集:

static void kernfs_init_inode(struct kernfs_node *kn, struct inode *inode)
{
  kernfs_get(kn);
  inode->i_private = kn;
  inode->i_mapping->a_ops = &kernfs_aops;
  inode->i_op = &kernfs_iops;
  set_default_inode_attr(inode, kn->mode);
  kernfs_refresh_inode(kn, inode);
  /* initialize inode according to type */
  switch (kernfs_type(kn)) {
  case KERNFS_DIR:
    inode->i_op = &kernfs_dir_iops;
    inode->i_fop = &kernfs_dir_fops;
    if (kn->flags & KERNFS_EMPTY_DIR)
      make_empty_dir_inode(inode);
    break;
  case KERNFS_FILE:
    inode->i_size = kn->attr.size;
    inode->i_fop = &kernfs_file_fops;
    break;
  case KERNFS_LINK:
    inode->i_op = &kernfs_symlink_iops;
    break;
  default:
    BUG();
  }
  unlock_new_inode(inode);
}

kernfs_file_fops 定义为:

const struct file_operations kernfs_file_fops = {
  .read   = kernfs_fop_read,
  .write    = kernfs_fop_write,
  .llseek   = generic_file_llseek,
  .mmap   = kernfs_fop_mmap,
  .open   = kernfs_fop_open,
  .release  = kernfs_fop_release,
  .poll   = kernfs_fop_poll,
};

所以shell环境下的命令最终会调用到kernfs_fop_read函数,在后者调用的readbuffer

中,将调用到总线属性对象中的show函数:

ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
  struct seq_file *m = file->private_data;
  size_t copied = 0;
  loff_t pos;
  size_t n;
  void *p;
  int err = 0;
  mutex_lock(&m->lock);
  /*
   * seq_file->op->..m_start/m_stop/m_next may do special actions
   * or optimisations based on the file->f_version, so we want to
   * pass the file->f_version to those methods.
   *
   * seq_file->version is just copy of f_version, and seq_file
   * methods can treat it simply as file version.
   * It is copied in first and copied out after all operations.
   * It is convenient to have it as  part of structure to avoid the
   * need of passing another argument to all the seq_file methods.
   */
  m->version = file->f_version;
  /* Don't assume *ppos is where we left it */
  if (unlikely(*ppos != m->read_pos)) {
    while ((err = traverse(m, *ppos)) == -EAGAIN)
      ;
    if (err) {
      /* With prejudice... */
      m->read_pos = 0;
      m->version = 0;
      m->index = 0;
      m->count = 0;
      goto Done;
    } else {
      m->read_pos = *ppos;
    }
  }
  /* grab buffer if we didn't have one */
  if (!m->buf) {
    m->buf = seq_buf_alloc(m->size = PAGE_SIZE);
    if (!m->buf)
      goto Enomem;
  }
  /* if not empty - flush it first */
  if (m->count) {
    n = min(m->count, size);
    err = copy_to_user(buf, m->buf + m->from, n);
    if (err)
      goto Efault;
    m->count -= n;
    m->from += n;
    size -= n;
    buf += n;
    copied += n;
    if (!m->count)
      m->index++;
    if (!size)
      goto Done;
  }
  /* we need at least one record in buffer */
  pos = m->index;
  p = m->op->start(m, &pos);
  while (1) {
    err = PTR_ERR(p);
    if (!p || IS_ERR(p))
      break;
    err = m->op->show(m, p);
    if (err < 0)
      break;
    if (unlikely(err))
      m->count = 0;
    if (unlikely(!m->count)) {
      p = m->op->next(m, p, &pos);
      m->index = pos;
      continue;
    }
    if (m->count < m->size)
      goto Fill;
    m->op->stop(m, p);
    kvfree(m->buf);
    m->count = 0;
    m->buf = seq_buf_alloc(m->size <<= 1);
    if (!m->buf)
      goto Enomem;
    m->version = 0;
    pos = m->index;
    p = m->op->start(m, &pos);
  }
  m->op->stop(m, p);
  m->count = 0;
  goto Done;
Fill:
  /* they want more? let's try to get some more */
  while (m->count < size) {
    size_t offs = m->count;
    loff_t next = pos;
    p = m->op->next(m, p, &next);
    if (!p || IS_ERR(p)) {
      err = PTR_ERR(p);
      break;
    }
    err = m->op->show(m, p);
    if (seq_has_overflowed(m) || err) {
      m->count = offs;
      if (likely(err <= 0))
        break;
    }
    pos = next;
  }
  m->op->stop(m, p);
  n = min(m->count, size);
  err = copy_to_user(buf, m->buf, n);
  if (err)
    goto Efault;
  copied += n;
  m->count -= n;
  if (m->count)
    m->from = n;
  else
    pos++;
  m->index = pos;
Done:
  if (!copied)
    copied = err;
  else {
    *ppos += copied;
    m->read_pos += copied;
  }
  file->f_version = m->version;
  mutex_unlock(&m->lock);
  return copied;
Enomem:
  err = -ENOMEM;
  goto Done;
Efault:
  err = -EFAULT;
  goto Done;
}


目录
相关文章
|
10月前
|
Linux 程序员 容器
总线,设备,驱动与class(二)
总线,设备,驱动与class
62 1
|
10月前
|
Linux 容器
总线,设备,驱动与class(三)
总线,设备,驱动与class(三)
61 1
|
10月前
|
Linux API SoC
总线驱动--SPI驱动(上)
总线驱动--SPI驱动
201 0
|
10月前
|
Linux
总线驱动--SPI驱动(下)
总线驱动--SPI驱动
147 0
|
10月前
|
传感器 算法 Linux
总线驱动---IIC驱动(上)
总线驱动---IIC驱动
77 0
|
10月前
|
传感器 Linux
总线驱动---IIC驱动(下)
总线驱动---IIC驱动
61 0
|
10月前
|
Ubuntu 调度
usb摄像头驱动-core层usb设备的注册
usb摄像头驱动-core层usb设备的注册
59 0
|
11月前
|
存储 Linux 开发者
【Linux学习笔记】设备驱动模型详解——总线、设备、驱动和类
设备驱动是计算机系统中的重要组成部分,它们允许操作系统与硬件交互。设备驱动模型是一种通用的抽象框架,用于描述操作系统如何管理硬件设备。这里我们将介绍设备驱动模型中的四个关键概念:总线、设备、驱动和类。
502 0
|
12月前
LED模板驱动程序的改造:总线设备驱动模型
LED模板驱动程序的改造:总线设备驱动模型
83 0
RK3399平台开发系列讲解(高速设备驱动篇)6.51、PCI总线信号定义
RK3399平台开发系列讲解(高速设备驱动篇)6.51、PCI总线信号定义
107 0
RK3399平台开发系列讲解(高速设备驱动篇)6.51、PCI总线信号定义