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

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

设备与驱动的绑定

在继续讨论设备与驱动的话题之前,先来看看Linux设备驱动模型中的一个重要概念:设备与驱动的绑定(binding)。这里的绑定,简单地说就是将一个设备与能控制它的驱动程序结合到一起的行为。两个内核对象间的结合自然是靠各自背后的数据结构中的某些成员来完成。

总线在设备与驱动绑定的过程中发挥着核心作用:总线相关的代码屏蔽了大量底层琐碎的技术细节,为驱动程序员们提供了一组使用友好的外在接囗,从而简化了驱动程序的开发工作。在总线上发生的两类事件将导致设备与驱动绑定行为的发生:一是通过device_register函数向某一bus上注册一设备,这种情况下内核除了将该设备加入到bus上的设备链表的尾端,同时会试图将此设备与总线上的所有驱动对象进行绑定操作(当然,操作归操作,能否成功则是另外一回事头二是通过driver_register将某一驱动注册到其所属的bus上,内核此时除了将该驱动对象加入到bus的所有驱动对象构成的链表的尾部,也会试图将该驱动与其上的所有设备进行绑定操作。

下面从代码的角度看看设备与驱动的绑定到底意味着什么。当调用device_register向某一bus上注册一设备对象时,device_bind_driver函数会被调用来将该设备与它的驱动程序绑定起来:

int device_bind_driver(struct device *dev)
{
  int ret;
  ret = driver_sysfs_add(dev);
  if (!ret)
    driver_bound(dev);
  return ret;
}

其中driver_sysfs_add用来在sysfs文件系统中建立绑定的设备与驱动程序之间的链接符号文件。而driver_bound函数中关于绑定的最核心的代码为:

klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);

用来将设备private结构中的knode_driver节点加入到与该设备绑定的驱动private结构中的klist_devices链表中。所以所谓设备与驱动的绑定,从代码的允度看,其实是在两者之间通过某种数据结构的使用建立了一种关联的渠道。

设备

设备在内核中的数据结构为struct device,该类型的实例是对具体设备的一个抽象:

//设备驱动模型中的device结构体
struct device {
  struct device   *parent;//指向设备的“父”设备,它所连接的设备
  struct device_private *p;//用于保存设备驱动核心部分的私有数据
  struct kobject kobj;//嵌入的struct kobject对象实例
  const char    *init_name; //设备对象的名称。在将该设备对象加入到系统中时,内核会把init_name设置成kobj成员的名称,后者在sysfs中表现为一个目录。
  /* 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 */
  struct device_driver *driver;//用以表示当前设备是否已经与它的driver进行了绑定,如果该值为NULL,说明当前设备还没有找到它的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 fwnode_handle  *fwnode; /* firmware 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;//接入class链表时所需要的klist节点
  struct class    *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;
};

系统中的每个设备都是一个structdevice对象,内核为容纳所有这些设备定义了一个kset—devicesk_set,作为系统中所有struct device类型内核对象的容器。同时,内核将系统中的设备分为两大类:block和char。每类对应一个内核对象,分别为sysfs_dev_block_kobj和sysfs_devchar_kobj,自然地这些内核对象也在sysfs文件树中占有对应的入口点,block和char内核对象的上级内核对象为dev_kobj。设备相关的这些事儿发生得比较早,在Linux系统初始化期间由devices_init来完成,有关设备的故事就从那里开始:

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

这个函数的操作反映到/sys文件目录下,就是生成了/sys/devices、/sys/dev、/sys/dev/block和/sys/dev/char。

Linux内核中针对设备的主要操作有:

  • device_initialize
    用于设备的初始化,该函数的实现为:
void device_initialize(struct device *dev)
{
  dev->kobj.kset = devices_kset;
  kobject_init(&dev->kobj, &device_ktype);
  INIT_LIST_HEAD(&dev->dma_pools);
  mutex_init(&dev->mutex);
  lockdep_set_novalidate_class(&dev->mutex);
  spin_lock_init(&dev->devres_lock);
  INIT_LIST_HEAD(&dev->devres_head);
  device_pm_init(dev);
  set_dev_node(dev, -1);
}

这个函数主要用于初始化dev的一些成员,其中dev->kobj.kset=devicek_set表明了dev所属的kset对象为device_skset,device_pm_init用来初始化dev与电源管理相关的部分。

  • device_register
    用来向系统注册一个设备,在源码中的实现为:
int device_register(struct device *dev)
{
  device_initialize(dev);
  return device_add(dev);
}

所以device_register内部除了调用device_imtialize来初始化dev对象外,还会通过device_add的调用将设备对象dev加入到系统中。

device_add是个非常重要的函数,对于理解Linux设备驱动模型非常有帮助。该函数的原型为:

int device_add(struct device *dev)
{
  struct device *parent = NULL;
  struct kobject *kobj;
  struct class_interface *class_intf;
  int error = -EINVAL;
  dev = get_device(dev);
  if (!dev)
    goto done;
  if (!dev->p) {
    error = device_private_init(dev);
    if (error)
      goto done;
  }
  /*
   * for statically allocated devices, which should all be converted
   * some day, we need to initialize the name. We prevent reading back
   * the name, and force the use of dev_name()
   */
  if (dev->init_name) {
    dev_set_name(dev, "%s", dev->init_name);
    dev->init_name = NULL;
  }
  /* subsystems can specify simple device enumeration */
  if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
    dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
  if (!dev_name(dev)) {
    error = -EINVAL;
    goto name_error;
  }
  pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
  parent = get_device(dev->parent);
  kobj = get_device_parent(dev, parent);
  if (kobj)
    dev->kobj.parent = kobj;
  /* use parent numa_node */
  if (parent)
    set_dev_node(dev, dev_to_node(parent));
  /* first, register with generic layer. */
  /* we require the name to be set before, and pass NULL */
  error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
  if (error)
    goto Error;
  /* notify platform of device entry */
  if (platform_notify)
    platform_notify(dev);
  error = device_create_file(dev, &dev_attr_uevent);
  if (error)
    goto attrError;
  error = device_add_class_symlinks(dev);
  if (error)
    goto SymlinkError;
  error = device_add_attrs(dev);
  if (error)
    goto AttrsError;
  error = bus_add_device(dev);
  if (error)
    goto BusError;
  error = dpm_sysfs_add(dev);
  if (error)
    goto DPMError;
  device_pm_add(dev);
  if (MAJOR(dev->devt)) {
    error = device_create_file(dev, &dev_attr_dev);
    if (error)
      goto DevAttrError;
    error = device_create_sys_dev_entry(dev);
    if (error)
      goto SysEntryError;
    devtmpfs_create_node(dev);
  }
  /* Notify clients of device addition.  This call must come
   * after dpm_sysfs_add() and before kobject_uevent().
   */
  if (dev->bus)
    blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
               BUS_NOTIFY_ADD_DEVICE, dev);
  kobject_uevent(&dev->kobj, KOBJ_ADD);
  bus_probe_device(dev);
  if (parent)
    klist_add_tail(&dev->p->knode_parent,
             &parent->p->klist_children);
  if (dev->class) {
    mutex_lock(&dev->class->p->mutex);
    /* tie the class to the device */
    klist_add_tail(&dev->knode_class,
             &dev->class->p->klist_devices);
    /* notify any interfaces that the device is here */
    list_for_each_entry(class_intf,
            &dev->class->p->interfaces, node)
      if (class_intf->add_dev)
        class_intf->add_dev(dev, class_intf);
    mutex_unlock(&dev->class->p->mutex);
  }
done:
  put_device(dev);
  return error;
 SysEntryError:
  if (MAJOR(dev->devt))
    device_remove_file(dev, &dev_attr_dev);
 DevAttrError:
  device_pm_remove(dev);
  dpm_sysfs_remove(dev);
 DPMError:
  bus_remove_device(dev);
 BusError:
  device_remove_attrs(dev);
 AttrsError:
  device_remove_class_symlinks(dev);
 SymlinkError:
  device_remove_file(dev, &dev_attr_uevent);
 attrError:
  kobject_uevent(&dev->kobj, KOBJ_REMOVE);
  kobject_del(&dev->kobj);
 Error:
  cleanup_device_parent(dev);
  put_device(parent);
name_error:
  kfree(dev->p);
  dev->p = NULL;
  goto done;
}

我们把deviceadd函数中一些比较重要的功能分成下面几个部分来描述:

在sysfs文件系统中建立系统硬件拓扑关系结构图

建立代表dev的内核对象kobject的层次关系,简单地说就是为dev找到它的上级(parent)内核对象,这个层次关系决定了dev加入到系统后在sysfs文件树中的目录层次。代码中关于这种层次关系的建立虽然不是很难理解,但是比较烦琐,而且牵涉到dev->class成员,根据dev->class与dev->parent的值分成四种情况讨论:

  1. dev->class和dev->parent都为空
    由于在对dev对象调用device_initialize函数时,曾指定了dev所属的kset为devices_kset:dev->kobJ.kset=devices_kset,所以这种情况下在将dev->kobJ加入系统时,内核会将devices_kset所对应的kobJ为dev->kobj的parent,所以dev->kobJ.parent=devices_kset->kobJ。由于deviceskset是在devices_init中建立的设备的顶层kset,这种情况下dev对象将会在/sys/devices目录下产生一个新的目录/sys/devices/dev->init_name。
  2. dev->class为空,dev->parent不为空
    这种情况下对应dev对象的新目录将建立在dev->parent->kobJ对应的目录之下。
  3. dev->class不为空,dev->parent为空
    dev->class不为空意味着该dev属于某一class,对于这种情况系统将为dev->kobj.parent建立一个虚拟上层对象“virtual”如此,将对象加入系统将会在/sys/device/virtusl中产生一个新的目录/sys/devices/virtual/dev->initname。
  4. dev->cls和dev->parent都不为空
    这种情况下要看dev->parent->class是否为空,如果不为空,则dev的parent kobject为dev->parent->kobj,即父设备的内嵌kobject。

如果dev->parent->class为空,则内核需要在dev->class->p->class_dirs.list中寻找是否有满足条件的kobject对象k,使得k->parent=&parent->kobJ,如果找到那么dev->kobJ的parent kobj就是dev设备的父设备的内嵌kobject,否则需要重新生成一个kobJect对象作为dev->kobj的父kobJ

调用device_create_sys_dev_entry(dev)建立一个新的链接,该链接的目的和源取决于dev->class。链接源的产生:

static struct kobject *device_to_dev_kobj(struct device *dev)
{
  struct kobject *kobj;
  if (dev->class)
    kobj = dev->class->dev_kobj;
  else
    kobj = sysfs_dev_char_kobj;
  return kobj;
}

假设dev对象的设备号major=251,minor=0,设备名称为dev->initname,那么:

如果dev->class为空,则新链接为/sys/dev/char/251 0 /sys/devices/dev->initname;

如果dev->class不为空,那么链接文件的源头将在dev->cJass->devkobJ所对应的目录下产生,目的链接则为/sys/devices/vtrtual/dev->initname。

调用bus_add_device(dev),在/sys/bus/devices目录下创建一个链接文件,指向/sys/devices/dev->initname。

假设设备的名称dev->initname为“demodev”,主次设备号分别为251和0,dev->class为空,那么通过deviceadd向系统添加“demodev”设备后,sysfs文件树中反映的系统硬件拓扑结构如图9-5所示,图中阴影部分为device_add新增的目录和链接文件:

在sysfs文件树中创建与该dev对象对应的属性文件

uevent_attr是dev对象的一个属性,其定义如下:

一个体现设备驱动模型中总线、设备与驱动相互沟通的重要函数调用

bus_probe_device(dev),该函数的实现如下:

void bus_probe_device(struct device *dev)
{
  struct bus_type *bus = dev->bus;
  struct subsys_interface *sif;
  int ret;
  if (!bus)
    return;
  if (bus->p->drivers_autoprobe) {
    ret = device_attach(dev);
    WARN_ON(ret < 0);
  }
  mutex_lock(&bus->p->mutex);
  list_for_each_entry(sif, &bus->p->interfaces, node)
    if (sif->add_dev)
      sif->add_dev(dev, sif);
  mutex_unlock(&bus->p->mutex);
}

如果满足if语句中的条件,将会调用device_attach试图将当前的设备绑定到它的驱动程序上,device_attach进行绑定的核心代码如下:

int device_attach(struct device *dev)
{
  int ret = 0;
  device_lock(dev);
  if (dev->driver) {
    if (klist_node_attached(&dev->p->knode_driver)) {
      ret = 1;
      goto out_unlock;
    }
    ret = device_bind_driver(dev);
    if (ret == 0)
      ret = 1;
    else {
      dev->driver = NULL;
      ret = 0;
    }
  } else {
    ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
    pm_request_idle(dev);
  }
out_unlock:
  device_unlock(dev);
  return ret;
}

如果dev->driver不为空,表明当前的设备对象dev已经和它的驱动程序进行了绑定,这种情况下只需调用device_bind_driver(dev)在文件树中建立dev与其驱动程序之间的互联关系。

如果dev->driver为空,表明当前设备对象还没有和它的驱动程序绑定,此时需要遍历dev所在总线dev->bus上挂载的所有驱动程序对象:

bus_for_each_drv(dev->bus, NULL, dev, __device_attach);

然后对遍历过程中的每个驱动程序对象drv,调用__device_attach(drv,dev)进行绑定:

static int __device_attach(struct device_driver *drv, void *data)
{
  struct device *dev = data;
  if (!driver_match_device(drv, dev))
    return 0;
  return driver_probe_device(drv, dev);
}

函数中的driver_match_device(drv,dev)用来判断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;
}

意味着如果当前drv对象所在的总线定义了match方法,那么就调用它来进行是否匹配的判断:如果总线没有定义match,那么driver_match_device(drv,dev)函数返回1,表明匹配成功,不成功返回0,device_attach函数继续对dev->bus上的下一个驱动程序对象进行匹配操作。

如果driver_match_device匹配成功,那么将调用driver_probe_device(drv,dev)将drv和dev进行绑定,这个工作实际上是由really_probe函数来完成的:

static int really_probe(struct device *dev, struct device_driver *drv)
{
  int ret = 0;
  int local_trigger_count = atomic_read(&deferred_trigger_count);
  atomic_inc(&probe_count);
  pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
     drv->bus->name, __func__, drv->name, dev_name(dev));
  WARN_ON(!list_empty(&dev->devres_head));
  dev->driver = drv;
  /* If using pinctrl, bind pins now before probing */
  ret = pinctrl_bind_pins(dev);
  if (ret)
    goto probe_failed;
  if (driver_sysfs_add(dev)) {
    printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
      __func__, dev_name(dev));
    goto probe_failed;
  }
  if (dev->pm_domain && dev->pm_domain->activate) {
    ret = dev->pm_domain->activate(dev);
    if (ret)
      goto probe_failed;
  }
  if (dev->bus->probe) {
    ret = dev->bus->probe(dev);
    if (ret)
      goto probe_failed;
  } else if (drv->probe) {
    ret = drv->probe(dev);
    if (ret)
      goto probe_failed;
  }
  if (dev->pm_domain && dev->pm_domain->sync)
    dev->pm_domain->sync(dev);
  driver_bound(dev);
  ret = 1;
  pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
     drv->bus->name, __func__, dev_name(dev), drv->name);
  goto done;
probe_failed:
  devres_release_all(dev);
  driver_sysfs_remove(dev);
  dev->driver = NULL;
  dev_set_drvdata(dev, NULL);
  if (dev->pm_domain && dev->pm_domain->dismiss)
    dev->pm_domain->dismiss(dev);
  switch (ret) {
  case -EPROBE_DEFER:
    /* Driver requested deferred probing */
    dev_dbg(dev, "Driver %s requests probe deferral\n", drv->name);
    driver_deferred_probe_add(dev);
    /* Did a trigger occur while probing? Need to re-trigger if yes */
    if (local_trigger_count != atomic_read(&deferred_trigger_count))
      driver_deferred_probe_trigger();
    break;
  case -ENODEV:
  case -ENXIO:
    pr_debug("%s: probe of %s rejects match %d\n",
       drv->name, dev_name(dev), ret);
    break;
  default:
    /* driver matched but the probe failed */
    printk(KERN_WARNING
           "%s: probe of %s failed with error %d\n",
           drv->name, dev_name(dev), ret);
  }
  /*
   * Ignore errors returned by ->probe so that the next driver can try
   * its luck.
   */
  ret = 0;
done:
  atomic_dec(&probe_count);
  wake_up(&probe_waitqueue);
  return ret;
}

函数首先将当前驱动程序对象drv赋值给dev->driver,然后,如果dev->bus->probe不为空,即dev所在的总线定义了probe方法,则调用之,否则如果drv对象定义了该方法,就调用drv->probe(dev),所以现在知道了我们driver中定义的probe函数什么时候会被调用到。这种设计机制给驱动程序提供了一个探测硬件的机会,即在其probe函数中作出判断:当前的设备是不是自己所支持的,以及当前设备是否处于工作状态等。驱动程序中实现的probe函数如果认为探测成功,那么应该返回0。

最后的driver_bound(dev)用来将驱动程序的一些数据信息加入到dev对象中。

  • device_unregister
用来将一个设备从系统中注销掉,其实现为:
void device_unregister(struct device *dev)
{
  pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
  device_del(dev);
  put_device(dev);
}

函数的重点在device_del中:

void device_del(struct device *dev)
{
  struct device *parent = dev->parent;
  struct class_interface *class_intf;
  /* Notify clients of device removal.  This call must come
   * before dpm_sysfs_remove().
   */
  if (dev->bus)
    blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
               BUS_NOTIFY_DEL_DEVICE, dev);
  dpm_sysfs_remove(dev);//设备电源管理函数,关闭本设备电源同时邇知其父设备(如果有的话)
  if (parent)//有父设备,将当前设备从父设备所属链表中删除
    klist_del(&dev->p->knode_parent);
  if (MAJOR(dev->devt)) {//如果dev设备对象的主设备号不为0
    devtmpfs_delete_node(dev);//动态删除设备节点文件
    device_remove_sys_dev_entry(dev);
    device_remove_file(dev, &dev_attr_dev);//删除设备的属性文件
  }
  if (dev->class) {
    device_remove_class_symlinks(dev);
    mutex_lock(&dev->class->p->mutex);
    /* notify any interfaces that the device is now gone */
    list_for_each_entry(class_intf,
            &dev->class->p->interfaces, node)
      if (class_intf->remove_dev)
        class_intf->remove_dev(dev, class_intf);
    /* remove the device from the class list */
    klist_del(&dev->knode_class);
    mutex_unlock(&dev->class->p->mutex);
  }
  device_remove_file(dev, &dev_attr_uevent);
  device_remove_attrs(dev);
  bus_remove_device(dev);
  device_pm_remove(dev);
  driver_deferred_probe_del(dev);
  /* Notify the platform of the removal, in case they
   * need to do anything...
   */
  if (platform_notify_remove)
    platform_notify_remove(dev);
  if (dev->bus)
    blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
               BUS_NOTIFY_REMOVED_DEVICE, dev);
  kobject_uevent(&dev->kobj, KOBJ_REMOVE);
  cleanup_device_parent(dev);
  kobject_del(&dev->kobj);
  put_device(parent);
}

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

热门文章

最新文章