> Linux教程 > linux基础 >

LDM上层建筑之dev---局部窥探

首先看下sys/devices怎么来的?
在初始的启动汇编中会跳到start_kernel---->rest_init---->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND)---->do_basic_setup(void)---->driver_init(void)---->devices_init();
 
int __init devices_init(void)
{
 devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
 if (!devices_kset)
  return -ENOMEM;
 return 0;
}
 
了解了kset后,可以知道在sys下面有了devices目录。
 
在smdk2440_machine_init函数中调用platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));

static struct platform_device *smdk2440_devices[] __initdata = {
 &s3c_device_usb,
 &s3c_device_lcd,
 &s3c_device_wdt,
 &s3c_device_i2c,
 &s3c_device_iis,
 &s3c_device_usbgadget,
 &s3c_device_ts,
 &s3c_device_rtc,
 &s3c_device_sdi,
};
所以platform_add_devices对这个结构体数组中的每一个设备做一次platform_device_register操作,我们用s3c_device_rtc为例。
 
static struct resource s3c_rtc_resource[] = {
 [0] = {
  .start = S3C24XX_PA_RTC,
  .end   = S3C24XX_PA_RTC + 0xff,
  .flags = IORESOURCE_MEM,
 },
 [1] = {
  .start = IRQ_RTC,
  .end   = IRQ_RTC,
  .flags = IORESOURCE_IRQ,
 },
 [2] = {
  .start = IRQ_TICK,
  .end   = IRQ_TICK,
  .flags = IORESOURCE_IRQ
 }
};
struct platform_device s3c_device_rtc = {
 .name    = "s3c2410-rtc",
 .id    = -1,
 .num_resources   = ARRAY_SIZE(s3c_rtc_resource),
 .resource   = s3c_rtc_resource,
};
 
那么,
int platform_device_register(struct platform_device *pdev)
{
 device_initialize(&pdev->dev);
 return platform_device_add(pdev);
}
 
1、device_initialize(&pdev->dev);
这里pdev是&s3c_device_rtc
void device_initialize(struct device *dev)
{
 dev->kobj.kset = devices_kset;   //devices_kset就是文章开头devices_init中的devices_kset
 kobject_init(&dev->kobj, &device_ktype);   //初始化dev->kobj
 klist_init(&dev->klist_children, klist_children_get,
     klist_children_put);
 INIT_LIST_HEAD(&dev->dma_pools);
 INIT_LIST_HEAD(&dev->node);
 init_MUTEX(&dev->sem);
 spin_lock_init(&dev->devres_lock);
 INIT_LIST_HEAD(&dev->devres_head);
 device_init_wakeup(dev, 0);
 set_dev_node(dev, -1);
}
 
2、platform_device_add(pdev);
int platform_device_add(struct platform_device *pdev)
{
 int i, ret = 0;
 if (!pdev)
  return -EINVAL;
 if (!pdev->dev.parent)
  pdev->dev.parent = &platform_bus;
//
说明:
struct device platform_bus = {
 .bus_id  = "platform",
};
 
//
 
 pdev->dev.bus = &platform_bus_type;
//
//struct bus_type platform_bus_type = {
// .name  = "platform",
// .dev_attrs = platform_dev_attrs,
// .match  = platform_match,
// .uevent  = platform_uevent,
// .suspend = platform_suspend,
// .suspend_late = platform_suspend_late,
// .resume_early = platform_resume_early,
// .resume  = platform_resume,
//};
 
//
 if (pdev->id != -1)
  snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,
    pdev->id);
 else
  strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);
//因为在
//struct platform_device s3c_device_rtc = {
// .name    = "s3c2410-rtc",
// .id    = -1,
// .num_resources   = ARRAY_SIZE(s3c_rtc_resource),
// .resource   = s3c_rtc_resource,
//};中将id定为了-1
//所以这里走else
//将pdev->name拷贝到pdev->dev.bus_id中,这里我们的name是"s3c2410-rtc"。
 
 for (i = 0; i < pdev->num_resources; i++) {
  struct resource *p, *r = &pdev->resource[i];
  if (r->name == NULL)
   r->name = pdev->dev.bus_id;
  p = r->parent;
  if (!p) {
   if (r->flags & IORESOURCE_MEM)
    p = &iomem_resource;
   else if (r->flags & IORESOURCE_IO)
    p = &ioport_resource;
  }
  if (p && insert_resource(p, r)) {
   printk(KERN_ERR
          "%s: failed to claim resource %d/n",
          pdev->dev.bus_id, i);
   ret = -EBUSY;
   goto failed;
  }
 }
 
//给pdev的resource做初始化
 
 
 pr_debug("Registering platform device '%s'. Parent at %s/n",
   pdev->dev.bus_id, pdev->dev.parent->bus_id);
 ret = device_add(&pdev->dev); //关键的一步,见下面的展开
 if (ret == 0)
  return ret;
 failed:
 while (--i >= 0)
  if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))
   release_resource(&pdev->resource[i]);
 return ret;
}
 
 
int device_add(struct device *dev)
{
 struct device *parent = NULL;
 struct class_interface *class_intf;
 int error;
 dev = get_device(dev);
 if (!dev || !strlen(dev->bus_id)) {
  error = -EINVAL;
  goto Done;
 }
 pr_debug("device: '%s': %s/n", dev->bus_id, __FUNCTION__);
 parent = get_device(dev->parent);
 
//因为前面有pdev->dev.parent = &platform_bus;
//所以这里parent指向platform_bus。

 setup_parent(dev, parent);
//
static void setup_parent(struct device *dev, struct device *parent)
{
 struct kobject *kobj;
 kobj = get_device_parent(dev, parent);
 if (kobj)
  dev->kobj.parent = kobj;
}
 
看get_device_parent(dev, parent);
 
因为没有定义CONFIG_SYSFS_DEPRECATED这个宏,所以这个函数用下面的
用下面的函数意味着所有的设备,无论是物理设备还是虚拟设备,都放在sys/devices目录下,
整个系统中的设备树统一在/sys/devices/目录中。
//*******************************//
这个函数如下:

static struct kobject *get_device_parent(struct device *dev,
      struct device *parent)
{
 int retval;
 if (dev->class) {                             //没有看到定义dev->class,不走这边
  struct kobject *kobj = NULL;
  struct kobject *parent_kobj;
  struct kobject *k;
  /*
   * If we have no parent, we live in "virtual".
   * Class-devices with a non class-device as parent, live
   * in a "glue" directory to prevent namespace collisions.
   */
  if (parent == NULL)
   parent_kobj = virtual_device_parent(dev);
  else if (parent->class)
   return &parent->kobj;
  else
   parent_kobj = &parent->kobj;
  /* find our class-directory at the parent and reference it */
  spin_lock(&dev->class->class_dirs.list_lock);
  list_for_each_entry(k, &dev->class->class_dirs.list, entry)
   if (k->parent == parent_kobj) {
    kobj = kobject_get(k);
    break;
   }
  spin_unlock(&dev->class->class_dirs.list_lock);
  if (kobj)
   return kobj;
  /* or create a new class-directory at the parent device */
  k = kobject_create();
  if (!k)
   return NULL;
  k->kset = &dev->class->class_dirs;
  retval = kobject_add(k, parent_kobj, "%s", dev->class->name);
  if (retval < 0) {
   kobject_put(k);
   return NULL;
  }
  /* do not emit an uevent for this simple "glue" directory */
  return k;
 }
 if (parent)                    //走这里,返回&parent->kobj
  return &parent->kobj;
 return NULL;
}
//所以呢,dev->kobj.parent = kobj;将dev->kobj.parent 设为了platform_bus(name为"platform")这个device结构体的kobj
 
继续所以,pdev->dev.parent = &platform_bus;以及dev->kobj.parent = kobj;两个地方的父子关系都正确。
//***************************************//
 
 
 /* first, register with generic layer. */
 error = kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev->bus_id);
//
将rtc这个dev的kobj作为第一个参数,dev->kobj.parent(platform_bus的kobj)作为第二个参数,而name为
dev->bus_id
而在前面说过了:
//将pdev->name拷贝到pdev->dev.bus_id中,这里我们的name是"s3c2410-rtc"。
所以这里的name就是"s3c2410-rtc".
那个可以看到这里的kobject_add操作会在devices下建立platform目录,platform目录下建立s3c2410-rtc目录。
 
//
 if (error)
  goto Error;
 /* notify platform of device entry */
 if (platform_notify)
  platform_notify(dev);
 /* notify clients of device entry (new way) */
 if (dev->bus)
  blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
          BUS_NOTIFY_ADD_DEVICE, dev);
 
//因为在platform_device_add函数开头处有pdev->dev.bus = &platform_bus_type;
//所以这个if会执行    但我看不懂这个会做什么事,先搁着吧。。。
//#define BUS_NOTIFY_ADD_DEVICE  0x00000001 /* device added */
// 是个回调机制,通知platform_bus_type上所有的设备ADD_DEVICE。
 
 
 
 error = device_create_file(dev, &uevent_attr);
//在s3c2410-rtc目录下创建uevent属性文件,所有注册的dev都会生成这个文件
//具体uevent做什么用,以后再学习
 

 if (error)
  goto attrError;
 if (MAJOR(dev->devt)) {
  error = device_create_file(dev, &devt_attr);
  if (error)
   goto ueventattrError;
 }
//// 如果有设备号,则生成相应的文件, s3c_device_rtc没有。
///* 如果有,则在 s3c2410-rtc 目录下生成名为 “dev"的属性文件,这样udev就能读
//取该属性文件获得设备号,从而在/dev目录下创建设备节点 */

 error = device_add_class_symlinks(dev);
/// 由于 s3c_device_rtc没有指定class,所以不会生成相应的链接
 if (error)
  goto SymlinkError;
 error = device_add_attrs(dev);
//具体看下函数(生成类的属性文件和其他的属性文件)
/********************************************
static int device_add_attrs(struct device *dev)
{
 struct class *class = dev->class;
 struct device_type *type = dev->type;
 int error;
 if (class) {                                       //因为class不存在,这里不执行
  error = device_add_attributes(dev, class->dev_attrs);
  if (error)
   return error;
 }
 if (type) {                                     //type也不存在,这里也不执行
  error = device_add_groups(dev, type->groups);
  if (error)
   goto err_remove_class_attrs;
 }
 error = device_add_groups(dev, dev->groups);   //这里要执行,但我没找到dev->groups是什么,所以也没做什么
 if (error)
  goto err_remove_type_groups;
 return 0;
 err_remove_type_groups:
 if (type)
  device_remove_groups(dev, type->groups);
 err_remove_class_attrs:
 if (class)
  device_remove_attributes(dev, class->dev_attrs);
 return error;
}
 
*******************************************************/

 if (error)
  goto AttrsError;
 error = dpm_sysfs_add(dev);//建立power的属性文件
 if (error)
  goto PMError;
 device_pm_add(dev);//// 添加到活跃设备的链表里
 error = bus_add_device(dev);
/**************************************
 
/**
 * bus_add_device - add device to bus
 * @dev: device being added
 *
 * - Add the device to its bus's list of devices.
 * - Create link to device's bus.
 */
int bus_add_device(struct device *dev)
{
 struct bus_type *bus = bus_get(dev->bus);
//首先获取dev->bus,就是说 bus 是指向platform_bus_type的

error = 0;
 if (bus) {
  pr_debug("bus: '%s': add device %s/n", bus->name, dev->bus_id);
  error = device_add_attrs(bus, dev);
  //*******************************
static int device_add_attrs(struct bus_type *bus, struct device *dev)
{
 int error = 0;
 int i;
 if (!bus->dev_attrs)
  return 0;
 for (i = 0; attr_name(bus->dev_attrs[i]); i++) {
  error = device_create_file(dev, &bus->dev_attrs[i]);
  if (error) {
   while (--i >= 0)
    device_remove_file(dev, &bus->dev_attrs[i]);
   break;
  }
 }
 return error;
}
 
//因为struct bus_type platform_bus_type = {
 .name  = "platform",
 .dev_attrs = platform_dev_attrs,
...
}
所以不会return 0;看后面的for循环
建立文件名为modalias的属性文件
//
一般在bus的uevent函数中,都会添加MODALIAS环境变量,设置成dev的名字。这样,uevent传到用户空间后,就可以通过对MODALIAS的匹配自动加载模块。这样的bus例子有platform和I2C等等。
 
 
****************************//               
if (error)
   goto out_put;
  error = sysfs_create_link(&bus->p->devices_kset->kobj,
      &dev->kobj, dev->bus_id);
//*************************
sysfs_create_link(struct kobject * kobj, struct kobject * target, const char * name)的注释:
/**
 * sysfs_create_link - create symlink between two objects.
 * @kobj: object whose directory we're creating the link in.
 * @target: object we're pointing to.
 * @name:  name of the symlink.
 */
可以看出来这个函数在kobj所表示的目录中新建一个name为name的链接文件,该链接文件指向target这个kobject代表的目录。
具体到这句话,是说在/sys/bus/platform/devices目录生成一个
名为”s3c2410-rtc"的链接文件,指向/sys/devices/platform/s3c2410-rtc
************************//

  if (error)
   goto out_id;
  error = sysfs_create_link(&dev->kobj,
    &dev->bus->p->subsys.kobj, "subsystem");
//在s3c2410-rtc 目录下生成一个链接文件,其名为”subsystem",指向其所属的
platform_bus_type的目录/sys/bus/platform

  if (error)
   goto out_subsys;
  error = make_deprecated_bus_links(dev);
//这一行没执行

  if (error)
   goto out_deprecated;
 }
 return 0;
out_deprecated:
 sysfs_remove_link(&dev->kobj, "subsystem");
out_subsys:
 sysfs_remove_link(&bus->p->devices_kset->kobj, dev->bus_id);
out_id:
 device_remove_attrs(bus, dev);
out_put:
 bus_put(dev->bus);
 return error;
}
 
*********************************/
if (error)
  goto BusError;
 kobject_uevent(&dev->kobj, KOBJ_ADD);
//// 通过uevents设置几个环境变量并通知用户空间,以便调用程序来完成相关设置     uevent以后来学习

 bus_attach_device(dev);
 
//***********************************
 
/**
 * bus_attach_device - add device to bus
 * @dev: device tried to attach to a driver
 *
 * - Add device to bus's list of devices.
 * - Try to attach to driver.
 */
void bus_attach_device(struct device *dev)
{
 struct bus_type *bus = dev->bus;
 int ret = 0;
 if (bus) {
  dev->is_registered = 1;     //表明dev已经注册了
  if (bus->p->drivers_autoprobe)  //drivers_autoprobe会在bus_register函数中有bus->p = priv;和priv-//>drivers_autoprobe = 1;所以这里的if要跑
   ret = device_attach(dev);  //这个见下面
  WARN_ON(ret < 0);
  if (ret >= 0)     //如果bus添加device成功了,就将dev的总线节点加到bus的dev链表中。
   klist_add_tail(&dev->knode_bus, &bus->p->klist_devices);
  else            //如果失败了,那么dev注册失败。
   dev->is_registered = 0;
 }
}
 
 int device_attach(struct device *dev)
{
 int ret = 0;
 down(&dev->sem);
 if (dev->driver) {                                   //如果这个地方的dev有对应的driver了,那么就调用device_bind_driver函数
  ret = device_bind_driver(dev);              //bind a driver to one device  将他们捆绑起来
/*****************
/**
 * device_bind_driver - bind a driver to one device.
 * @dev: device.
 *
 * Allow manual attachment of a driver to a device.
 * Caller must have already set @dev->driver.
********************/
int device_bind_driver(struct device *dev)
{
 int ret;
 ret = driver_sysfs_add(dev);                   //又和sysfs有关了,先调用一次sysfs_create_link函数来创建一个链接文件,链接文件
//的name是"s3c2410-rtc",该链接文件位于/buses/platform/drivers/s3c2410-rtc下面,指向devices/platform/s3c2410-rtc目录。如果这个创建成功了,再调用一次sysfs_create_link函数来创建一个链接文件,链接文件的name是“driver”,该链接文件位于
devices/platform/s3c2410-rtc目录下,指向/buses/platform/drivers/s3c2410-rtc目录。
 if (!ret)      
  driver_bound(dev);    //如果上面的sysfs链接文件创建成功了,那么执行driver_bound.
//将dev加入到与之相关联的driver的devices链表中(注:一个dev只能由一个driver来驱动,但是一个driver却可以驱动多个dev,driver的devices链表中的设备就是该driver所支持的所有设备)。
在driver_bound(dev)中有
//#define BUS_NOTIFY_BOUND_DRIVER  0x00000003/* driver bound to device */
// 是个回调机制,通知platform_bus_type上所有的设备。
//最后一步,是将dev加入到与之相关联的driver的devices链表中。
//klist_add_tail(&dev->knode_driver, &dev->driver->p->klist_devices);
//单看下面:
/******************
/**
 * klist_add_tail - Initialize a klist_node and add it to back.
 * @n: node we're adding.
 * @k: klist it's going on.
 */
void klist_add_tail(struct klist_node * n, struct klist * k)
{
 klist_node_init(k, n);
 add_tail(k, n);
}
 
klist当一个链表看,klist_node当链表上的一个节点。
struct klist {
 spinlock_t  k_lock;
 struct list_head k_list;
 void   (*get)(struct klist_node *);
 void   (*put)(struct klist_node *);
};
 
struct klist_node {
 struct klist  * n_klist;
 struct list_head n_node;
 struct kref  n_ref;
 struct completion n_removed;
};
 
static void klist_node_init(struct klist * k, struct klist_node * n)      //初始化函数
{
 INIT_LIST_HEAD(&n->n_node);
 init_completion(&n->n_removed);
 kref_init(&n->n_ref);
 n->n_klist = k;        //主要的初始化操作,将节点的n_klist指针指向klist。                     
 if (k->get)
  k->get(n);
}
 
static void add_tail(struct klist * k, struct klist_node * n)
{
 spin_lock(&k->k_lock);
 list_add_tail(&n->n_node, &k->k_list);  将n->n_node加到k->k_list这个双向链表中
 spin_unlock(&k->k_lock);
}
至此,将dev加入到与之相关联的driver的devices链表中
 
***********************/
 return ret;
}
 
*******************/
  if (ret == 0)
   ret = 1;
  else {
   dev->driver = NULL;
   ret = 0;
  }
 } else {
  ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
//如果dev->driver为空,那就在bus中给dev找driver。遍历该总线上所有的driver,执行一次__device_attach(),看能不能将设备关联(attach)到某个已登记的驱动上去。
/***************
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
       void *data, int (*fn)(struct device_driver *, void *))
{
 struct klist_iter i;
 struct device_driver *drv;
 int error = 0;
 if (!bus)                                     //既然函数名是bus_for_each_drv,没有bus肯定不行
  return -EINVAL;          
 klist_iter_init_node(&bus->p->klist_drivers, &i,
        start ? &start->p->knode_bus : NULL);   //因为start为null,所以这里的第三个参数就为null
/******
void klist_iter_init_node(struct klist * k, struct klist_iter * i, struct klist_node * n)
{
 i->i_klist = k;     //i->i_klist=bus->p->klist_drivers
 i->i_head = &k->k_list;    //i->i_head=bus->p->klist_drivers->k_list
 i->i_cur = n;                  //i->i_cur=null
 if (n)
  kref_get(&n->n_ref);
}
 
 
 
******/
 while ((drv = next_driver(&i)) && !error)
  error = fn(drv, data);                                      //通过while循环来遍历bus上的bus->p->klist_drivers所有driver,然后调用
//__device_attach(struct device_driver *drv, void *data)函数(其中data为dev)
 
 /**********
static int __device_attach(struct device_driver *drv, void *data)
{
 struct device *dev = data;
 return driver_probe_device(drv, dev);  //driver 探测device
/******
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
 int ret = 0;
 if (!device_is_registered(dev))        //首先检查device是否注册了
  return -ENODEV;
 if (drv->bus->match && !drv->bus->match(dev, drv))   //drv->bus->match =platform_match函数,
//执行这个函数,比较dev的name和driver的name,如果相等,返回1,所以这里的if就不成立,所以要跑下面的
//really_paobe函数
  goto done;
 pr_debug("bus: '%s': %s: matched device %s with driver %s/n",
   drv->bus->name, __FUNCTION__, dev->bus_id, drv->name);
 ret = really_probe(dev, drv);       // 这里真正开始调用用户在device_driver 中注册的porbe()例程
done:
 return ret;
}
********/
 
static int really_probe(struct device *dev, struct device_driver *drv)
{
 int ret = 0;
 atomic_inc(&probe_count);
 pr_debug("bus: '%s': %s: probing driver %s with device %s/n",
   drv->bus->name, __FUNCTION__, drv->name, dev->bus_id);
 WARN_ON(!list_empty(&dev->devres_head));
 dev->driver = drv;                                 //将dev和drv联系起来   将匹配的driver指针关联到 dev,
 if (driver_sysfs_add(dev)) {
  printk(KERN_ERR "%s: driver_sysfs_add(%s) failed/n",
   __FUNCTION__, dev->bus_id);
  goto probe_failed;
 }
//又和sysfs有关了,先调用一次sysfs_create_link函数来创建一个链接文件,链接文件
//的name是"s3c2410-rtc",该链接文件位于/buses/platform/drivers/s3c2410-rtc下面,指向devices/platform/s3c2410-rtc目录。如果这个创建成功了,再调用一次sysfs_create_link函数来创建一个链接文件,链接文件的name是“driver”,该链接文件位于
devices/platform/s3c2410-rtc目录下,指向/buses/platform/drivers/s3c2410-rtc目录。
 
 
 if (dev->bus->probe) { //如果dev->bus->probe  存在,调用这个probe函数,但是这个没有看到 在platform_bus_type没有设置
  ret = dev->bus->probe(dev);
  if (ret)
   goto probe_failed;
 } else if (drv->probe) {  //driver的probe函数,这里是
//static struct platform_driver s3c2410_rtcdrv = {
 .probe  = s3c_rtc_probe,
 .remove  = s3c_rtc_remove,
 .suspend = s3c_rtc_suspend,
 .resume  = s3c_rtc_resume,
 .driver  = {
  .name = "s3c2410-rtc",
  .owner = THIS_MODULE,
 },
};//有定义的,在分析上层建筑的时候再详细看这个probe函数
  ret = drv->probe(dev);
  if (ret)
   goto probe_failed;
 }
 driver_bound(dev);   ////将dev加入到与之相关联的driver的devices链表中   dev和drv相互关联起来了
 ret = 1;
 pr_debug("bus: '%s': %s: bound device %s to driver %s/n",
   drv->bus->name, __FUNCTION__, dev->bus_id, drv->name);
 goto done;
probe_failed:
 devres_release_all(dev);
 driver_sysfs_remove(dev);
 dev->driver = NULL;
 if (ret != -ENODEV && ret != -ENXIO) {
  /* driver matched but the probe failed */
  printk(KERN_WARNING
         "%s: probe of %s failed with error %d/n",
         drv->name, dev->bus_id, 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;
}

}
**********/
 

 klist_iter_exit(&i);
 return error;
}
 
 
***************/
 }
 up(&dev->sem);
 return ret;
}
 
*************************************//

 if (parent)        //将dev的父节点加到platform这个种的klist_chilren链表。
  klist_add_tail(&dev->knode_parent, &parent->klist_children);
 if (dev->class) {
  down(&dev->class->sem);
  /* tie the class to the device */
  list_add_tail(&dev->node, &dev->class->devices);
  /* notify any interfaces that the device is here */
  list_for_each_entry(class_intf, &dev->class->interfaces, node)
   if (class_intf->add_dev)
    class_intf->add_dev(dev, class_intf);
  up(&dev->class->sem);
 }
 Done:
 put_device(dev);
 return error;
 BusError:
 device_pm_remove(dev);
 PMError:
 if (dev->bus)
  blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
          BUS_NOTIFY_DEL_DEVICE, dev);
 device_remove_attrs(dev);
 AttrsError:
 device_remove_class_symlinks(dev);
 SymlinkError:
 if (MAJOR(dev->devt))
  device_remove_file(dev, &devt_attr);
 ueventattrError:
 device_remove_file(dev, &uevent_attr);
 attrError:
 kobject_uevent(&dev->kobj, KOBJ_REMOVE);
 kobject_del(&dev->kobj);
 Error:
 cleanup_device_parent(dev);
 if (parent)
  put_device(parent);
 goto Done;
}
 
 
 至此,platform_add_devices就走完了,但从这个分析过程来看,dev和bus、drv有很多很多的关系,要看完bus和drv后才可能
将三者的关系理清楚。
至少这个分析完了,sysfs下的devices目录就有内容了。
(责任编辑:IT)