Linux设备模型 - linux-2.6.24

本文深入解析Linux设备模型的核心概念和技术细节,包括总线、设备、驱动程序的基础结构,以及平台总线(platform bus)和PCI总线的具体实现。文章详细介绍了设备注册流程、设备与驱动程序的匹配机制,并通过具体实例展示了设备模型如何简化硬件抽象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

源码基线:Linux-2.6.24

设备模型概述

设备模型主要完成以下工作:
1.设备分类,以分层的架构对设备进行描述,隐藏设备内部的连接细节,对外清晰地展示可用的设备。
2.创建和管理设备的生命周期。
3.通过sysfs虚拟文件系统,向用户空间提供对设备的读写操作,获取设备的信息、改变设备的运行状态。

设备模型的结构组成:
1.总线。
所有的设备都通过总线相连,包括内部的虚拟总线。在内核中,用struct bus_type结构体来表示总线。

struct bus_type {  
    const char      * name;  
    struct kset     drivers;  
    struct kset     devices;  
}  
  • name是总线的名字。
  • kset driverskset devices,分别代表了总线的驱动程序及插入总线的所有设备集合。

2.设备。
每个设备实例用struct device结构体来表示。

struct device {  
    struct device       *parent;  
    struct kobject kobj;  
        char bus_id[BUS_ID_SIZE];  
    struct bus_type * bus;  
    struct device_driver *driver;  
}  
  • parent指向设备的所属父设备,通常是某种总线。
  • kobj表示本设备对象。
  • bus_id是总线上标识设备的ID信息,通常由字符串”<域编号>:<总线编号>:<设备编号>:<功能编号>”定义
  • bus标识了设备连接在哪个总线上。
  • driver管理设备的驱动程序。

3.驱动程序。
设备驱动程序设备模型可以跟踪所有注册的设备,驱动程序为设备提供服务,完成设备的工作。设备的驱动程序由结构体struct device_driver定义。

struct device_driver {  
    const char      * name;  
    struct bus_type     * bus;  
    struct kobject      kobj;  
        struct klist            klist_devices;  
}  
  • name是驱动程序的名字。
  • bus是指驱动程序所操作的总线类型。
  • kobj表示驱动程序服务的设备对象。
  • klist_devices是驱动程序当前能操作的设备链表。

platform虚拟总线

struct kobject与kset结构体

kobject与kset是组成设备模型的基本结构,为来表示设备模型的设备实例与设备层次关系。这两个结构体作为面向对象概念的基类,嵌套在其他结构体里使用。在sysfs中显示的每一个实例,都对应一个kobject。

/* 设备模型的基本结构。 
 * 在sysfs中显示的每一个对象,都对应一个kobject,它被用来与内核交互并创建它的可见表述。 
 * 内核用kobject结构将各个对象连接起来组成一个分层的结构体系 */  
struct kobject {  
    const char      * k_name;  
    struct kref     kref; /* 对象的引用计数。一个内核对象被创建时,需要知道对象存活的时间,跟踪生命周期的一个方法是使用引用计数,当内核中没有该对象的引用时,表示对象的生命周期结束,可以被删除 */  
    struct list_head    entry; /* 连接到kset建立层次结构 */  
    struct kobject      * parent; /* parent保存了分层结构中上一层节点kobject结构的指针。比如一个kobject结构表示了一个USB设备,它的parent指针可能指向了表示USB集线器的对象,而USB设备是插在USB集线器上的。parent指针最重要的用途是在sysfs分层结构中定位对象 */   
    struct kset     * kset; /* 一个kset是嵌入相同类型结构的kobject集合。每个kset内部,包含了自己的kobject。kset总是在sysfs中出现,一旦设置了kset并把它添加到系统中,将在sysfs中创建一个目录。kobject不必在sysfs中表示,但kset中每一个kobject成员都将在sysfs中得到表述 */  
    struct kobj_type    * ktype; /* 属性结构。kset中也有一个ktype,其使用优先于kobject的此处ktype,因此在典型应用中,kobject中的ktype成员被设置为NULL */  
    struct sysfs_dirent * sd; /* 指向sysfs下以k_name所命名生成的目录 */  
};  

struct kset {  
    struct kobj_type    *ktype;  
    struct list_head    list;  
    spinlock_t      list_lock;  
    struct kobject      kobj;  
    struct kset_uevent_ops  *uevent_ops;  
};  

假设有个总线X-bus,以及3个设备驱动A-driver、B-driver、C-driver,且这3个设备都属于同一类X-bus总线。那么kobject与kset在描述设备模型时,通常这样使用。在X-bus的描述结构体中包含kset结构体作为同一类型设备的集合;在描述设备驱动A、B、C的driver结构体中包含kset指针,指向所属的类型集合。每个driver都有一个kobject来表示驱动实例自身,并把所有集合按照加入的先后顺序通过list链表(kset.list与kobject.entry)连接起来。
这里写图片描述

注册platform总线

函数入口driver_init()

void __init driver_init(void)  
{  
    devices_init();  
    buses_init();  
    platform_bus_init();  
}  

devices_init()初始化一个kset实例devices_subsys,在sysfs下建立一个“devices”目录。

int __init devices_init(void)  
{  
    /* 由decl_subsys宏定义 */  
    return subsystem_register(&devices_subsys); 
}  

int subsystem_register(struct kset *s)  
{  
        return kset_register(s);  
}  

int kset_register(struct kset * k)  
{  
        kset_init(k);  
        err = kset_add(k);  
        if (err)  
                return err;  
        kobject_uevent(&k->kobj, KOBJ_ADD);  
}          

kset devices_subsys由decl_subsys宏定义,名字为“devices”,ktype为device_ktype。

#define decl_subsys(_name,_type,_uevent_ops) \  
struct kset _name##_subsys = { \  
         .kobj = { .k_name = __stringify(_name) }, \  
         .ktype = _type, \  
         .uevent_ops =_uevent_ops, \  
}  
decl_subsys(devices, &device_ktype, &device_uevent_ops);  

kset_init()初始化devices_subsys的kobject。

void kset_init(struct kset * k)  
{  
    kobject_init(&k->kobj);  
    INIT_LIST_HEAD(&k->list);  
    spin_lock_init(&k->list_lock);  
}  
void kobject_init(struct kobject * kobj)  
{  
        kref_init(&kobj->kref); /* 初始化kobject的引用计数为1 */  
        INIT_LIST_HEAD(&kobj->entry); /* 初始化entry链表 */  
}  

kset_add()在sysfs下建立以kset->kobject.k_name命名的目录。kobject_add()的作用主要是在该kobject的kobj->parent指向的目录下,创建以kobj->k_name命名的目录,并把kobj->kset指向对应的kset集合对象,同时把该kset集合对象通过list链表指向kobject的kobj->entry进行关联。

int kset_add(struct kset * k)  
{  
    return kobject_add(&k->kobj);  
}  
int kobject_add(struct kobject * kobj)  
{  
    int error = 0;  
    struct kobject * parent;  

    if (!(kobj = kobject_get(kobj))) /* 将devices_subsys的kobject引用计数增1 */  
        return -ENOENT;  
    if (!kobj->k_name) /* decl_subsys宏定义了k_name="devices" */  
        kobject_set_name(kobj, "NO_NAME"); /* 设置devices_subsys的kobject的名字为"devices" */  

    parent = kobject_get(kobj->parent); /* 将父设备的引用计数增1,由于devices_subsys初始化时没有指定父结点,所以这里是空操作,parent为NULL */  

    if (kobj->kset) { /* kobj->kset为NULL,此条件不成立不会执行 */  
        spin_lock(&kobj->kset->list_lock);  
        if (!parent)  
            parent = kobject_get(&kobj->kset->kobj);  

        list_add_tail(&kobj->entry,&kobj->kset->list);  
        spin_unlock(&kobj->kset->list_lock);  
        kobj->parent = parent;  
    }  

    error = create_dir(kobj); /* 根据kobject的名字"devices"在sysfs下建立相应的目录 */  
    return error;  
}  
static int create_dir(struct kobject * kobj)  
{  
        int error = 0;  
        if (kobject_name(kobj)) {  
                 error = sysfs_create_dir(kobj);  
                 if (!error) {  
                         if ((error = populate_dir(kobj)))  
                                sysfs_remove_dir(kobj);  
                 }  
        }  
        return error;  
}  
int sysfs_create_dir(struct kobject * kobj)  
{  
        struct sysfs_dirent *parent_sd, *sd;  
        int error = 0;  

        /* devices_subsys初始化时没有指定父结点,所以将sysfs_root作为父结点的sysfs目录 */  
        if (kobj->parent)  
                 parent_sd = kobj->parent->sd;  
        else  
                 parent_sd = &sysfs_root;  
        /* 在sysfs目录下,以devices_subsys的kobject的名字"devices"建立一个目录,并将建立的目录结点保存到kobj->sd */  
        error = create_dir(kobj, parent_sd, kobject_name(kobj), &sd);  
        if (!error)  
                 kobj->sd = sd;  
        return error;  
}  

devices_init()创建的模型:
这里写图片描述

buses_init()类似devices_init(),在sysfs目录下建立一个”bus”目录。

buses_init()创建的模型
这里写图片描述

platform_bus_init()完成虚拟总线platform的创建。

int __init platform_bus_init(void)  
{  
    /* 注册到devices目录中 */  
    device_register(&platform_bus);  
    /* 注册到bus目录中 */  
    bus_register(&platform_bus_type);  
}  

注册platform_bus

struct device platform_bus = {  
    .bus_id     = "platform", /* 目录名 */  
};  
/* 注册设备:初始化设备的数据结构,将其加入到数据结构的网络中。 
   完成设备注册后,可以在/sys/devices目录中看到 */  
int device_register(struct device *dev)  
{  
    device_initialize(dev); /* 初始化dev结构 */  
    return device_add(dev); /* 添加dev至目录 */  
}  

void device_initialize(struct device *dev)  
{  
    /* devices_subsys在之前devices_init()中已经注册。将设备kobject的kset集合指向 
       devices_subsys */  
    kobj_set_kset_s(dev, devices_subsys);  
    /* 初始化设备引用计数为1,同时将其上一层的kset集合对象devices_subsys的引用计 
       数也加1 */  
    kobject_init(&dev->kobj);  
    klist_init(&dev->klist_children, klist_children_get,  
           klist_children_put);  
}  

int device_add(struct device *dev)  
{  
    struct device *parent = NULL;  
    struct class_interface *class_intf;  
    int error = -EINVAL;  

    /* 将设备的引用计数加1 */  
    get_device(dev);  

    kobject_set_name(&dev->kobj, "%s", dev->bus_id); /* bus_id="platform" */  
    /* 在device_initialize()中设置了platform_bug.kobject.kset=&devices_subsys, 
       所以在kobject_add()会设置platform_bus.kobj.parent=&devices_subsys.kobj。 
       因此这里与devices_subsys、bus_subsys不一样,不是以sysfs_root作为sysfs的 
       父结点目录,而是以devices_subsys作为sysfs的父结点目录 */  
    kobject_add(&dev->kobj);   
}  

这里写图片描述

注册platform_bus_type

/* 在注册设备及驱动程序之前,需要先有总线。bus_register()函数向系统添加一个新总 
   线 */  
struct bus_type {  
    const char      * name; /* 总线的文本名称,用于在sysfs文件系统中标识总线 */  
    struct module       * owner;  

    /* 与总线关联的所有设备和驱动程序使用devices和drivers成员,作为集合进行管理。 
       内核还会创建两个链表(klist_devices和klist_drivers)来保存相同的数据,这些 
       链表使内核能够快速扫描所有资源(设备和驱动程序),kset保证了与sysfs文件系统 
       的自动集成。subsys提供与总线子系统的关联,出现在/sys/bus/xxx */  
    struct kset     subsys;  
    struct kset     drivers; /* 总线的驱动程序 */  
    struct kset     devices; /* 插入总线的所有设备 */  
    struct klist        klist_devices;  
    struct klist        klist_drivers;  

    struct blocking_notifier_head bus_notifier;  

    struct bus_attribute    * bus_attrs;  
    struct device_attribute * dev_attrs; /* 指向一个为每个加入总线的设备建立的 
                                默认属性链表 */  
    struct driver_attribute * drv_attrs;  

    /* 试图查找与给定设备匹配的驱动程序 */  
    int     (*match)(struct device * dev, struct device_driver * drv);  
    int     (*uevent)(struct device *dev, struct kobj_uevent_env *env);  
    /* 在有必要将驱动程序关联到设备时,会调用probe。该函数检测设备在系统中是否 
       真正存在 */  
    int     (*probe)(struct device * dev);  
    /* 删除驱动程序和设备之间的关联。例如,在将可热挺拔的设备从系统中移除时,会 
       调用该函数 */  
    int     (*remove)(struct device * dev);  
};  

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

/* 注册新的总线 */  
int bus_register(struct bus_type * bus)  
{  
    int retval;  

    kobject_set_name(&bus->subsys.kobj, "%s", bus->name);  

    bus->subsys.kobj.kset = &bus_subsys;  
    /* 通过嵌入的kset类型成员subsys,将新总线添加到总线子系统 */  
    retval = subsystem_register(&bus->subsys);  
    if (retval)  
        goto out;  

    retval = bus_create_file(bus, &bus_attr_uevent); /* bus_attr_uevent由BUS_ATTR宏定义,在sysfs下创建uevent文件 */  
    if (retval)  
        goto bus_uevent_fail;  

    /* 总线需要了解相关设备及其驱动程序的所有有关信息,因此总线对二者注册了kset。 
       两个kset分别是drivers和devices,都将总线作为父结点 */  
    kobject_set_name(&bus->devices.kobj, "devices");  
    bus->devices.kobj.parent = &bus->subsys.kobj;  
    kset_register(&bus->devices);  

    kobject_set_name(&bus->drivers.kobj, "drivers");  
    bus->drivers.kobj.parent = &bus->subsys.kobj;  
    bus->drivers.ktype = &driver_ktype;  
    kset_register(&bus->drivers);  

    klist_init(&bus->klist_devices, klist_devices_get, klist_devices_put);  
    klist_init(&bus->klist_drivers, NULL, NULL);  

    bus->drivers_autoprobe = 1; /* 默认开启总线的自动探测功能 */  
    add_probe_files(bus); /* 在sysfs下创建drivers_autoprobe与drivers_probe文件 */  
}  

这里写图片描述

经过以上的注册过程后,注册生成的sysfs目录结构如下:

/sys
├── bus
│ └── platform
│ ├── devices
│ ├── drivers
│ ├── drivers_autoprobe
│ ├── drivers_probe
│ └── uevent
└── devices
└── platform
└── uevent

注册platform设备

现在我们要向platform注册一个atheros芯片的设备struct platform_devie ath79_wmac_device。注册的方式是通过调用platform_device_register()函数实现。

static struct resource ath79_wmac_resources[] = {  
    {  
        .flags  = IORESOURCE_MEM,  
    }, {  
        .flags  = IORESOURCE_IRQ,  
    },  
};  

static struct platform_device ath79_wmac_device = {  
    .name       = "ath9k", /* 默认名 */  
    .id     = -1,      
    .resource   = ath79_wmac_resources,  
    .num_resources  = ARRAY_SIZE(ath79_wmac_resources),      
};  

static void __init ar933x_wmac_setup(void)  
{  
    ath79_wmac_device.name = "ar933x_wmac"; /* 重命名 */  

    ath79_wmac_resources[0].start = AR933X_WMAC_BASE;  
    ath79_wmac_resources[0].end = AR933X_WMAC_BASE + AR933X_WMAC_SIZE - 1;  
    ath79_wmac_resources[1].start = ATH79_CPU_IRQ_IP2;  
    ath79_wmac_resources[1].end = ATH79_CPU_IRQ_IP2;  
}  

void __init ath79_register_wmac(u8 *cal_data, u8 *mac_addr)  
{  
    ar933x_wmac_setup();  
    platform_device_register(&ath79_wmac_device);  
}  

int platform_device_register(struct platform_device * pdev)  
{  
    device_initialize(&pdev->dev);  
    return platform_device_add(pdev);  
}  

int platform_device_add(struct platform_device *pdev)  
{  
    if (!pdev->dev.parent)  
        pdev->dev.parent = &platform_bus; /* 指明父设备是platform_bus */  

    pdev->dev.bus = &platform_bus_type; /* 指明设备归属总线platform_bus_type */  

    if (pdev->id != -1)  
        snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,  
             pdev->id);  
    else /* ath79_wmac_device.id = -1,所以执行这里,bus_id = "ar933x_wmac" */  
        strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);  

    /* 有两个IO资源,MEM与IRQ */  
    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; /* 赋值"ar933x_wmac" */  

        p = r->parent;  
        if (!p) {  
            if (r->flags & IORESOURCE_MEM)  
                p = &iomem_resource; /* parent为空,父结点设置为iomem_resource */  
            else if (r->flags & IORESOURCE_IO)  
                p = &ioport_resource;  
        }  

        if (p && insert_resource(p, r)) { /* 把新IO资源插入IO资源树 */  
            printk(KERN_ERR  
                   "%s: failed to claim resource %d\n",  
                   pdev->dev.bus_id, i);  
            ret = -EBUSY;  
            goto failed;  
        }  
    }  

    device_add(&pdev->dev);  
} 

到这里ar933x_wmac的目录就创建好了。

/sys
├── bus
│ └── platform
│ ├── devices
│ ├── drivers
│ ├── drivers_autoprobe
│ ├── drivers_probe
│ └── uevent
└── devices
└── platform
├── ar933x_wmac
└── uevent

device_add()这个函数里,还有一项重要的工作,通过调用bus_attach_device()这个函数,把同一总线类型的设备添加总线链表里,方便后续的搜索查找;同理,在注册驱动时,也会把驱动添加到链表里。
添加设备时,会通过bus_for_each_drv()搜索现有的驱动链表,查找是否有匹配的驱动,如果有,则执行相应的调用__device_attach()。同理,在添加驱动时,会通过bus_for_each_dev()搜索现有的设备链表,如果有,则执行相应的调用__driver_attach()
我们来看下,注册设备时,添加链表及驱动调用的过程。由于驱动还未注册,所以此时,还不会调用。后面的注册驱动分析过程中,我们再来看是如何调用的。

/* 将设备添加到bus->klist_devices */  
void bus_attach_device(struct device * dev)  
{  
    struct bus_type *bus = dev->bus;  
    int ret = 0;  

    if (bus) {  
        dev->is_registered = 1;  
        if (bus->drivers_autoprobe) /* bus_register()已经默认开启 */  
            ret = device_attach(dev);  
        WARN_ON(ret < 0);  
        if (ret >= 0) /* 这里把device加入到bus的klist_devices链表里 */  
            klist_add_tail(&dev->knode_bus, &bus->klist_devices);  
        else  
            dev->is_registered = 0;  
    }  
}  

int device_attach(struct device * dev)  
{  
    int ret = 0;  

    down(&dev->sem);  
    if (dev->driver) { /* 驱动还未注册,因此执行else */  
        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);  
    }  
    up(&dev->sem);  
    return ret;  
}  

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;  
    /* 由于还未注册驱动,所以klist_drivers为空不会执行直接return error */  
    klist_iter_init_node(&bus->klist_drivers, &i,  
                 start ? &start->knode_bus : NULL);  
    while ((drv = next_driver(&i)) && !error)  
        error = fn(drv, data);  
    klist_iter_exit(&i);  
    return error;  
}  

这里写图片描述

注册platform驱动

现在我们要向platform注册一个atheros芯片的驱动struct platform ar724x_pci_driver。注册的方式是通过调用platform_driver_register()函数实现。

static struct platform_driver ar724x_pci_driver = {  
    .probe = ar724x_pci_probe,  
    .driver = {  
        .name = "ar724x-pci",  
        .owner = THIS_MODULE,  
    },  
};  

static int __init ar724x_pci_init(void)  
{  
    return platform_driver_register(&ar724x_pci_driver);  
}  

postcore_initcall(ar724x_pci_init);  

#define postcore_initcall(fn)       __define_initcall("2",fn,2)  

#define __define_initcall(level,fn,id) \  
    static initcall_t __initcall_##fn##id __attribute_used__ \  
    __attribute__((__section__(".initcall" level ".init"))) = fn  

ar724x_pci_init()由__define_initcall宏定义,这个宏的作用是把ar724x_pci_init()函数指针安置到.initcall2.init段。在链接脚本vmlinux.lds里,定义了此段。do_initcalls()遍历调用.initcallx.init段里的函数,因此ar724x_pci_init()被调用了。

.initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {   
    __initcall_start = .;  
    INITCALLS  
    __initcall_end = .;  
}  

#define INITCALLS                           \  
    *(.initcall0.init)                      \  
    *(.initcall0s.init)                     \  
    *(.initcall1.init)                      \  
    *(.initcall1s.init)                     \  
    *(.initcall2.init)                      \  
    *(.initcall2s.init)                     \  
    *(.initcall3.init)                      \  
    *(.initcall3s.init)                     \  
    *(.initcall4.init)                      \  
    *(.initcall4s.init)                     \  
    *(.initcall5.init)                      \  
    *(.initcall5s.init)                     \  
    *(.initcallrootfs.init)                     \  
    *(.initcall6.init)                      \  
    *(.initcall6s.init)                     \  
    *(.initcall7.init)                      \  
    *(.initcall7s.init)  

static void __init do_basic_setup(void)  
{  
    driver_init();  
    do_initcalls();   
}  

static void __init do_initcalls(void)  
{  
    for (call = __initcall_start; call < __initcall_end; call++) {  
        (*call)();  
    }  
}      

platform_driver_register()

int platform_driver_register(struct platform_driver *drv)  
{  
    drv->driver.bus = &platform_bus_type;  
    if (drv->probe)  
        drv->driver.probe = platform_drv_probe;  
    if (drv->remove)  
        drv->driver.remove = platform_drv_remove;  
    return driver_register(&drv->driver);  
}  

int driver_register(struct device_driver * drv)  
{  
    if ((drv->bus->probe && drv->probe) ||  
        (drv->bus->remove && drv->remove) ||  
        (drv->bus->shutdown && drv->shutdown)) {  
        printk(KERN_WARNING   
            "Driver '%s' needs updating - please use bus_type methods\n",   
            drv->name);  
    }  
    klist_init(&drv->klist_devices, NULL, NULL);  
    return bus_add_driver(drv);  
}  

/* 将一个新驱动程序添加到一个总线 */  
int bus_add_driver(struct device_driver *drv)  
{  
    struct bus_type * bus = bus_get(drv->bus); /* bus指向&pci_bus_type */  

    kobject_set_name(&drv->kobj, "%s", drv->name);  

    drv->kobj.kset = &bus->drivers; /* 指向&pci_bus_type.drivers */  
    kobject_register(&drv->kobj); /* 在/sys/bus/platform/drivers下 
                        生成ar724x-pci目录 */  
    if (drv->bus->drivers_autoprobe) {  
        driver_attach(drv);  
    }  
    klist_add_tail(&drv->knode_bus, &bus->klist_drivers);                          
}                

/* 迭代总线上的所有设备,使用驱动程序的match函数进行检测,确定是否有某些设备可 
   使用该驱动程序管理。最后,将驱动程序添加到总线上注册的所有驱动程序的链表中 */  
int driver_attach(struct device_driver * drv)  
{  
    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);  
}  

int bus_for_each_dev(struct bus_type * bus, struct device * start,  
             void * data, int (*fn)(struct device *, void *))  
{  
    struct klist_iter i;  
    struct device * dev;  

    /* 这里做的事情简单来说,就是通过搜索注册在总线上的设备链表bus->klist_device 
       ,对各个设备迭代执行__driver_attach(),执行时传递了两个参数,一个是device 
       ,一个是driver。在我们的这个例子里,device是ath79_wmac_device.dev,driver 
       是ar724x_pci_driver */  
    klist_iter_init_node(&bus->klist_devices, &i,  
                 (start ? &start->knode_bus : NULL));  
    while ((dev = next_device(&i)) && !error)  
        fn(dev, data);  
}  

这里写图片描述
/sys
├── bus
│ └── platform
│ ├── devices
│ ├── drivers
│ │ └──ar724x-pci
│ ├── drivers_autoprobe
│ ├── drivers_probe
│ └── uevent
└── devices
└── platform
├── ar933x_wmac
└── uevent

现在我们来看下__driver_attach(&ath79_wmac_device.dev, &ar724x_pci_driver)做了什么。

static int __driver_attach(struct device * dev, void * data)  
{  
    struct device_driver * drv = data;      
    down(&dev->sem);  
    if (!dev->driver) /* 驱动还未挂接,所以driver为空 */  
        driver_probe_device(drv, dev);  
    up(&dev->sem);  
}  

int driver_probe_device(struct device_driver * drv, struct device * dev)  
{  
    /* bus是指向platform_bus_type,其match函数是platform_match() */  
    if (drv->bus->match && !drv->bus->match(dev, drv))  
        goto done; /* 设备与驱动不匹配时,直接返回 */  

    ret = really_probe(dev, drv);  

done:  
    return ret;  
}  

/* match的作用是进行设备与驱动的匹配 */  
static int platform_match(struct device * dev, struct device_driver * drv)  
{  
    /* 通过dev获取其所被包含的platform_device结构体ath79_wmac_device */  
    struct platform_device *pdev = container_of(dev, struct platform_device, dev);  

    /* ath79_wmac_device.name = "ar933x_wmac",而ar724x_pci_driver.name =  
       "ar724x-pci",因此设备与驱动不匹配 */  
    return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);  
}  

现在我们再来注册一个驱动struct platform_driver ath_ahb_driver,正是ar933x_wmac的驱动。

static struct platform_driver ath_ahb_driver = {  
    .probe      = ath_ahb_probe,  
    .remove     = ath_ahb_remove,  
    .driver     = {  
        .name   = "ar933x_wmac",  
        .owner  = THIS_MODULE,  
    },  
};  

int ath_ahb_init(void)  
{  
    return platform_driver_register(&ath_ahb_driver);  
}  

其注册过程同之前分析的一样。现在__driver_attach(&ath79_wmac_device.dev, &ath_ahb_driver)时,platform_match()是匹配的了,接下来调用really_probe(&ath79_wmac_device.dev, &ath_ahb_driver)

static int really_probe(struct device *dev, struct device_driver *drv)  
{  
    dev->driver = drv; /* 这里对设备进行了驱动的挂接 */  

    if (dev->bus->probe) { /* platform_bus_type没有probe() */  
        dev->bus->probe(dev);  
    } else if (drv->probe) { /* ath_ahb_probe() */  
        drv->probe(dev);  
    }  
}  

static int ath_ahb_probe(struct platform_device *pdev)  
{  
    void __iomem *mem;  
    struct ath_softc *sc;  
    struct ieee80211_hw *hw;  
    struct resource *res;  
    const struct platform_device_id *id = platform_get_device_id(pdev);  
    int irq;  
    int ret = 0;  
    struct ath_hw *ah;  
    char hw_name[64];  

    /* 获取ath79_wmac_resources[0] */  
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  

    /* io空间映射 */  
    mem = devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res));  

    /* 获取ath79_wmac_resources[1] */  
    res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);  

    irq = res->start;  
    request_irq(irq, ath_isr, IRQF_SHARED, "ath9k", sc);  
}  

PCI总线

注册PCI总线

PCI总线的注册同platform总线,都是通过bus_register()函数进行注册。

struct bus_type pci_bus_type = {  
    .name       = "pci",  
    .match      = pci_bus_match,  
    .uevent     = pci_uevent,  
    .probe      = pci_device_probe,  
    .remove     = pci_device_remove,  
    .suspend    = pci_device_suspend,  
    .suspend_late   = pci_device_suspend_late,  
    .resume_early   = pci_device_resume_early,  
    .resume     = pci_device_resume,  
    .shutdown   = pci_device_shutdown,  
    .dev_attrs  = pci_dev_attrs,  
};  

#define __define_initcall(level,fn,id) \  
    static initcall_t __initcall_##fn##id __attribute_used__ \  
    __attribute__((__section__(".initcall" level ".init"))) = fn  

#define postcore_initcall(fn)       __define_initcall("2",fn,2)  

postcore_initcall(pci_driver_init);      

static int __init pci_driver_init(void)  
{  
    return bus_register(&pci_bus_type);  
}  

这里写图片描述
/sys
└── bus
└── pci
├── devices
├── drivers
├── drivers_autoprobe
├── drivers_probe
└── uevent

注册PCI驱动

现在我们要在PCI总线上注册一个自己的struct pci_driver dkpci_driver的PCI驱动,通过pci_register_driver()函数进行注册。

struct device_driver {  
    const char      * name; /* 标识驱动程序 */  
    struct bus_type     * bus; /* 指向一个表示总线的对象,并提供特定于总线的 
                            操作 */  
    struct kobject      kobj;  
    struct klist        klist_devices; /* 链表的表头,包括了驱动程序控制的所有 
                            设备的device实例。链表中的各个设备通过 
                            device->knode_driver彼此连接 */  
    struct klist_node   knode_bus; /* 连接一条总线上的所有设备 */  
    struct module       * owner;  
    const char      * mod_name;   
    struct module_kobject   * mkobj;  
    /* 检测系统中是否存在能够用该设备驱动程序处理的设备 */  
    int (*probe)    (struct device * dev);  
    /* 删除系统中的设备时会调用remove */  
    int (*remove)   (struct device * dev);  
};  

/* PCI驱动程序数据结构,表示通用内核代码和设备的底层硬件驱动程序之间的接口 */  
struct pci_driver {  
    struct list_head node;  
    char *name; /* 设备的文本标识符(驱动程序的模块名称) */  
    /* 必须为非空指针,以便调用probe */  
    const struct pci_device_id *id_table;     
    /* 新设备插入时调用,检测驱动程序是否支持某个PCI设备 */  
    int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);     
    /* 设备移除时调用 */  
    void (*remove) (struct pci_dev *dev);     
    int  (*suspend) (struct pci_dev *dev, pm_message_t state);    
    int  (*suspend_late) (struct pci_dev *dev, pm_message_t state);  
    int  (*resume_early) (struct pci_dev *dev);  
    int  (*resume) (struct pci_dev *dev);                     
    void (*shutdown) (struct pci_dev *dev);  

    struct pci_error_handlers *err_handler;  
    /* 用于建立与通用设备模型的关联 */  
    struct device_driver    driver;  
    struct pci_dynids dynids;  
};  

static struct pci_driver dkpci_driver = {  
        name:   "dkkernel",  
        id_table: dk_id_tbl,  
        probe: dk_pci_probe,  
        remove: dk_pci_remove,  
        suspend: dk_pci_suspend,  
        resume: dk_pci_resume,  
};  

INT32 bus_module_init(VOID)   
{  
    int status;  
    status = pci_register_driver(&dkpci_driver);  
}      

/* 注册PCI驱动程序,使用driver_register()传递到通用设备层 */  
static inline int __must_check pci_register_driver(struct pci_driver *driver)  
{  
    return __pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME);  
}  

int __pci_register_driver(struct pci_driver *drv, struct module *owner,  
              const char *mod_name)  
{  
    /* initialize common driver fields */  
    drv->driver.name = drv->name; /* "dkkernel" */  
    drv->driver.bus = &pci_bus_type;  
    drv->driver.owner = owner;  
    drv->driver.mod_name = mod_name;  
    drv->driver.kobj.ktype = &pci_driver_kobj_type;  

    spin_lock_init(&drv->dynids.lock);  
    INIT_LIST_HEAD(&drv->dynids.list);  

    /* register with core */  
    driver_register(&drv->driver);  

    error = pci_create_newid_file(drv);  
    if (error)  
        driver_unregister(&drv->driver);  

    return error;  
}  

这里写图片描述
/sys
└── bus
└── pci
├── devices
├── drivers
│ └──dkkernel
├── drivers_autoprobe
├── drivers_probe
└── uevent

接下来,开始自动探测设备过程driver_attach()

int driver_attach(struct device_driver * drv)  
{  
    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);  
}  

int bus_for_each_dev(struct bus_type * bus, struct device * start,  
             void * data, int (*fn)(struct device *, void *))  
{  
    klist_iter_init_node(&bus->klist_devices, &i,  
                 (start ? &start->knode_bus : NULL));  
    while ((dev = next_device(&i)))  
        fn(dev, data);  
    klist_iter_exit(&i);  
}  

遍历bus->klist_devices链表,然后调用__driver_attach()开始执行相应的probe()函数。在前面介绍platform总线时,platform设备可以通过platform_device_register()进行设备的注册,生成bus->klist_devices链表。那么,我们的这个PCI驱动是如何生成bus->klist_devices链表的呢?记住设备模型的三要素:总线、设备、驱动,缺一不可工作。

注册PCI设备

探测PCI设备,加入PCI核
现在我们来看下,klist_devices链表是如何生成的,这又是一个漫长的过程。

上面说到的struct pci_driver dkpci_driver这个PCI驱动,是atheros芯片的驱动,而atheros是基于MIPS的架构,因此接下来我们介绍下MIPS Linux内核的PCI设备探测过程。

PCI总线、PCI设备、PCI驱动每个PCI总线都使用struct pci_bus结构体的一个实例表示,每个PCI设备由struct pci_dev结构体的实例来表示,每个PCI控制器由struct pci_controller结构体的实例来表示。

struct pci_controller {  
    struct pci_controller *next; /* 指向下一个PCI控制器数据结构的指针。所有控制 
                器结构体会形成一个链表 */  
    struct pci_bus *bus; /* PCI控制器归属哪个PCI总线 */  
    struct pci_ops *pci_ops; /* PCI配置空间的读写操作接口 */  
    struct resource *mem_resource; /* PCI内存空间范围 */  
    unsigned long mem_offset; /* 内存空间地址偏移 */  
    struct resource *io_resource; /* PCI I/O空间范围 */  
    unsigned long io_offset; /* I/O空间地址偏移 */  
    unsigned long io_map_base; /* I/O映射的基地址 */  
    unsigned int index; /* PCI控制器编号 */  
    unsigned int need_domain_info; /* 域信息 */  
    int iommu; /* mmu */  

    /* PCI控制器的总线号读取与设置接口 */  
    int (*get_busno)(void);  
    void (*set_busno)(int busno);  
};  

struct pci_bus {  
    struct list_head node; /* 总线链表中的结点 */         
    struct pci_bus  *parent; /* (桥接器)总线归属哪个父总线 */     
    struct list_head children; /* PCI子总线链表 */     
    struct list_head devices; /* PCI总线上所有设备的链表 */     
    struct pci_dev  *self; /* 总线所归属的桥接器设备 */          
    struct resource *resource[PCI_BUS_NUM_RESOURCES]; /* 总线的存储资源地址 */  
    struct pci_ops  *ops; /* PCI配置空间的读写操作接口 */        
    void        *sysdata; /* 用于特定于硬件的扩展 */    
    struct proc_dir_entry *procdir; /* /proc/bus/pci中的目录项 */  
    unsigned char   number; /* 总线号 */         
    unsigned char   primary; /* 主桥接器编号 */     
    unsigned char   secondary; /* 次桥接器编号 */   
    unsigned char   subordinate; /* 下级总线的最大数目 */      
    char        name[48];  
};  

struct pci_dev {  
    struct list_head global_list; /* 在所有PCI设备的链表中的结点 */  
    struct list_head bus_list; /* 在各总线设备链表中的结点 */  
    struct pci_bus  *bus; /* 设备所在的总线 */  
    struct pci_bus  *subordinate; /* 桥接器设备接通的总线 */      
    void        *sysdata; /* 用于特定于硬件的扩展 */  
    struct proc_dir_entry *procent; /* /proc/bus/pci中的设备目录项 */      
    unsigned int    devfn; /* 编码过的设备和功能索引 */  
    unsigned short  vendor; /* 设备厂商编号 */  
    unsigned short  device; /* 设备编号 */  
    unsigned short  subsystem_vendor;  
    unsigned short  subsystem_device;  
    unsigned int    class; /* 3个字节(base、sub、prog-if) */  
    u8      revision; /* PCI修订版本号,class的最低字节 */  
    u8      hdr_type; /* PCI配置空间头部类型 */  
    u8      pcie_type; /* PCI-E设备/端口类型 */  
    u8      rom_base_reg; /* 使用哪个配置寄存器来控制ROM */  
    u8      pin; /* 设备使用的中断针脚 */    
    struct pci_driver *driver; /* 设备挂接的PCI驱动程序 */  
    struct  device  dev; /* 到通用设备模型的接口 */  
    int     cfg_size; /* 配置空间的长度 */  

    /* 不要直接访问中断线和基本地址寄存器,应该使用这里存储的值 */  
    unsigned int    irq;  
    struct resource resource[DEVICE_COUNT_RESOURCE];   
}      

struct pci_ops {  
    int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val);  
    int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val);  
};  

通过register_pci_controller()注册一个PCI总线控制器struct pci_controller loongson2e_pci_controller,把控制器加入到全局链表struct pci_controller *hose_head里。

static struct resource loongson2e_pci_mem_resource = {  
    .name   = "LOONGSON2E PCI MEM",  
    .start  = 0x14000000UL,  
    .end    = 0x1fffffffUL,  
    .flags  = IORESOURCE_MEM,  
};  

static struct resource loongson2e_pci_io_resource = {  
    .name   = "LOONGSON2E PCI IO MEM",  
    .start  = 0x00004000UL,  
    .end    = IO_SPACE_LIMIT,  
    .flags  = IORESOURCE_IO,  
};  

static struct pci_controller  loongson2e_pci_controller = {  
    .pci_ops        = &bonito64_pci_ops,  
    .io_resource    = &loongson2e_pci_io_resource,  
    .mem_resource   = &loongson2e_pci_mem_resource,  
    .mem_offset     = 0x00000000UL,  
    .io_offset      = 0x00000000UL,  
};  

struct pci_controller *hose_head, **hose_tail = &hose_head;  

register_pci_controller(&loongson2e_pci_controller);  

void __devinit register_pci_controller(struct pci_controller *hose)  
{  
    request_resource(&iomem_resource, hose->mem_resource);  
    request_resource(&ioport_resource, hose->io_resource);  

    *hose_tail = hose; /* 控制器加入到全局链表hose_head */  
    hose_tail = &hose->next;  
}  

pcibios_init()负责扫描PCI控制器链表,进行PCI设备的查找与添加。

static int __init pcibios_init(void)  
{  
    struct pci_controller *hose;  
    struct pci_bus *bus;  
    int next_busno;  
    int need_domain_info = 0;  

    /* 扫描PCI控制器链表,进行PCI设备的查找与添加  */  
    for (next_busno = 0, hose = hose_head; hose; hose = hose->next) {  

        if (!hose->iommu)  
            PCI_DMA_BUS_IS_PHYS = 1;  

        if (hose->get_busno && pci_probe_only)  
            next_busno = (*hose->get_busno)();  
        /* next_busno = 0,从0号总线开始扫描 */  
        bus = pci_scan_bus(next_busno, hose->pci_ops, hose);  
        hose->bus = bus;  
        need_domain_info = need_domain_info || hose->index;  
        hose->need_domain_info = need_domain_info;  
        if (bus) {  
            next_busno = bus->subordinate + 1;  
            /* Don't allow 8-bit bus number overflow inside the hose - 
               reserve some space for bridges. */  
            if (next_busno > 224) {  
                next_busno = 0;  
                need_domain_info = 1;  
            }  
        }  
    }  
}  

subsys_initcall(pcibios_init);  

static inline struct pci_bus *pci_scan_bus(int bus, struct pci_ops *ops, void *sysdata)  
{  
    struct pci_bus *root_bus;  
    root_bus = pci_scan_bus_parented(NULL, bus, ops, sysdata);  
    if (root_bus)  
        pci_bus_add_devices(root_bus);  
    return root_bus;  
}  

struct pci_bus *pci_scan_bus_parented(struct device *parent,  
        int bus, struct pci_ops *ops, void *sysdata)  
{  
    struct pci_bus *b;  

    b = pci_create_bus(parent, bus, ops, sysdata);  
    if (b)  
        b->subordinate = pci_scan_child_bus(b);  
    return b;  
}  

pci_create_bus()创建PCI总线。根据总线控制器struct pci_controller的index决定总线的域编号,总线号由pci_scan_bus()的第一参数决定。pcibios_init()是从0号总线开始扫描,因此对于loongson2e_pci_controller这个PCI控制器,pci_create_bus()添加了一个域编号为0,总线编号为0的PCI桥设备"pci0000:00"

/sys
└── devices
└── pci0000:00

pci_create_bus()还完成一些其他工作,把pci_bus.ops指向PCI控制器的pci_ops;把kset集合指向devices_subsys。

unsigned int pci_scan_child_bus(struct pci_bus *bus)  
{  
    unsigned int devfn, pass, max = bus->secondary;  
    struct pci_dev *dev;  

    /* 每条PCI总线支持32个插槽,每个插槽支持8个功能设备,所以逻辑上总共是支持 
       256个设备,所以devfn在256(0x100)内进行扫描 */  
    for (devfn = 0; devfn < 0x100; devfn += 8)  
        pci_scan_slot(bus, devfn);  

    /* 
     * After performing arch-dependent fixup of the bus, look behind 
     * all PCI-to-PCI bridges on this bus. 
     */  
    pr_debug("PCI: Fixups for bus %04x:%02x\n", pci_domain_nr(bus), bus->number);  
    pcibios_fixup_bus(bus);  
    for (pass=0; pass < 2; pass++)  
        list_for_each_entry(dev, &bus->devices, bus_list) {  
            if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE ||  
                dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)  
                max = pci_scan_bridge(bus, dev, max, pass);  
        }  

    return max;  
}  

int pci_scan_slot(struct pci_bus *bus, int devfn)  
{  
    int func, nr = 0;  
    int scan_all_fns;  


    for (func = 0; func < 8; func++, devfn++) {  
        struct pci_dev *dev;  

        dev = pci_scan_single_device(bus, devfn);  
        if (dev) {  
            nr++;  

            /* 
             * If this is a single function device, 
             * don't scan past the first function. 
             */  
            if (!dev->multifunction) {  
                if (func > 0) {  
                    dev->multifunction = 1;  
                } else {  
                    break;  
                }  
            }  
        } else {  
            if (func == 0)  
                break;  
        }  
    }  
    return nr;  
}  

struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)  
{  
    struct pci_dev *dev;  

    pci_scan_device(bus, devfn); /* 扫描设备 */  

    pci_device_add(dev, bus); /* 添加设备 */  

    return dev;  
}  

这里我们先介绍对PCI设备配置空间的读取操作方式,在内核中是通过一系列的pci_bus_read_config_xxx()pci_bus_write_config_xxx()进行读写,这此函数是由PCI_OP_READPCI_OP_WRITE宏定义实现的。函数内部是通过调用PCI总线控制器提供的pci_ops来对PCI设备的配置空间进行读写。

#define PCI_OP_READ(size,type,len) \  
int pci_bus_read_config_##size \  
    (struct pci_bus *bus, unsigned int devfn, int pos, type *value) \  
{                                   \  
    int res;                            \  
    unsigned long flags;                        \  
    u32 data = 0;                           \  
    if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;   \  
    spin_lock_irqsave(&pci_lock, flags);                \  
    res = bus->ops->read(bus, devfn, pos, len, &data);        \  
    *value = (type)data;                        \  
    spin_unlock_irqrestore(&pci_lock, flags);           \  
    return res;                         \  
}  

#define PCI_OP_WRITE(size,type,len) \  
int pci_bus_write_config_##size \  
    (struct pci_bus *bus, unsigned int devfn, int pos, type value)  \  
{                                   \  
    int res;                            \  
    unsigned long flags;                        \  
    if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER;   \  
    spin_lock_irqsave(&pci_lock, flags);                \  
    res = bus->ops->write(bus, devfn, pos, len, value);       \  
    spin_unlock_irqrestore(&pci_lock, flags);           \  
    return res;                         \  
}  

PCI_OP_READ(byte, u8, 1)  
PCI_OP_READ(word, u16, 2)  
PCI_OP_READ(dword, u32, 4)  
PCI_OP_WRITE(byte, u8, 1)  
PCI_OP_WRITE(word, u16, 2)  
PCI_OP_WRITE(dword, u32, 4) 

PCI配置空间寄存器:
这里写图片描述

PCI配置空间的地址格式:
这里写图片描述

接下来我们看下pci_scan_device()干了什么。

#define PCI_VENDOR_ID       0x00    /* 16 bits */  
#define PCI_HEADER_TYPE     0x0e    /* 8 bits */  

static struct pci_dev * __devinit  
pci_scan_device(struct pci_bus *bus, int devfn)  
{  
    struct pci_dev *dev;  
    u32 l;  
    u8 hdr_type;  
    int delay = 1;  

    if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))  
        return NULL;  

    /* some broken boards return 0 or ~0 if a slot is empty: */  
    if (l == 0xffffffff || l == 0x00000000 ||  
        l == 0x0000ffff || l == 0xffff0000)  
        return NULL;  

    /* Configuration request Retry Status */  
    while (l == 0xffff0001) {  
        msleep(delay);  
        delay *= 2;  
        if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, &l))  
            return NULL;  
        /* Card hasn't responded in 60 seconds?  Must be stuck. */  
        if (delay > 60 * 1000) {  
            printk(KERN_WARNING "Device %04x:%02x:%02x.%d not "  
                    "responding\n", pci_domain_nr(bus),  
                    bus->number, PCI_SLOT(devfn),  
                    PCI_FUNC(devfn));  
            return NULL;  
        }  
    }  

    if (pci_bus_read_config_byte(bus, devfn, PCI_HEADER_TYPE, &hdr_type))  
        return NULL;  

    dev = alloc_pci_dev();  
    if (!dev)  
        return NULL;  

    dev->bus = bus;  
    dev->sysdata = bus->sysdata;  
    dev->dev.parent = bus->bridge;  
    dev->dev.bus = &pci_bus_type;  
    dev->devfn = devfn;  
    dev->hdr_type = hdr_type & 0x7f;  
    dev->multifunction = !!(hdr_type & 0x80);  
    dev->vendor = l & 0xffff;  
    dev->device = (l >> 16) & 0xffff;  
    dev->cfg_size = pci_cfg_space_size(dev);  
    dev->error_state = pci_channel_io_normal;  
    set_pcie_port_type(dev);  

    /* Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer) 
       set this higher, assuming the system even supports it.  */  
    dev->dma_mask = 0xffffffff;  
    if (pci_setup_device(dev) < 0) {  
        kfree(dev);  
        return NULL;  
    }  

    return dev;  
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值