基于Atheros无线芯片的设备、驱动、虚拟网桥分析
2016-11-12 18:09
399 查看
!Warning:请确保能够访问图床Imgur,以正常显示图片
源码基线:linux-4.0-rc1 & OpenWrt 12.09 branch (Attitude Adjustment)
硬件:Atheros AR9331
设备分类,以分层的架构对设备进行描述,隐藏设备内部的连接细节,对外清晰地展示可用的设备。
创建和管理设备的生命周期。
通过sysfs虚拟文件系统,向用户空间提供对设备的读写操作,获取设备的信息、改变设备的运行状态。
设备模型的结构组成:
1.总线。
所有的设备都通过总线相连,包括内部的虚拟总线。在内核中,用
2.设备。
每个设备实例用struct device结构体来表示。
parent指向设备的所属父设备,通常是某种总线。
kobj表示本设备对象。
bus_id是总线上标识设备的ID信息,通常由字符串”<域编号>:<总线编号>:<设备编号>:<功能编号>”定义
3.驱动程序。
设备驱动程序设备模型可以跟踪所有注册的设备,驱动程序为设备提供服务,完成设备的工作。设备的驱动程序由结构体struct device_driver定义。
设备名称。
设备mac地址。由软件随机生成。
广播地址。
以太网类型、帧长度、帧头长度、mtu大小。
设备模型对象。
通过
空节点
添加第一个节点
添加第二个节点
假设有个总线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)连接起来。
devices_init()
“`c
/sys
├── dev
│ ├── block
│ └── char
└── devices
kset_create_and_add()
kobject_create_and_add()
buses_init()
“`c
/sys
├── bus
└── devices
└── system
platform_bus_init()
“`c
/sys
└── devices
└── platform
└── uevent /* -rw-r–r– */
device_register()
bus_register()
AR9331支持4个集成PHY的LAN口,及1个集成PHY的WAN口。4个LAN口通过GMII与CPU连接,WAN口可以配置使用指定的MII接口与CPU连接。
AR9331以太网交换控制器包含5个10/100M FE端口。
AR9331集成2个GE MAC。交换芯片的phy4 WAN口通过GEO的MII接口与CPU连接。
PHY0 ~ PHY4支持桥接模式相连,在桥接模式下,GE0必须处于复位模式,所有5个口都 作为LAN口,并通过CPU的GE1与MAC0连接。如果GE0单独连接到PHY,MAC5必须处于复位模式。
当用户配置用Kernel command line的参数
内核初始化时,根据vmlinux.lds的段安排,内核初始化函数
AR9331芯片寄存器描述:
struct net
linux每个进程有自己的命名空间,在clone进程时,为新进程分配命名空间。命名空间由全局变量
分配网络设备实例并注册
硬件初始化
linux下配置网桥的命令:
内核初始化时,注册了回调函数
图中
创建的网桥设备“br0”已经挂接了驱动程序
源码基线:linux-4.0-rc1 & OpenWrt 12.09 branch (Attitude Adjustment)
硬件:Atheros AR9331
设备模型概述
设备模型主要完成以下工作:设备分类,以分层的架构对设备进行描述,隐藏设备内部的连接细节,对外清晰地展示可用的设备。
创建和管理设备的生命周期。
通过sysfs虚拟文件系统,向用户空间提供对设备的读写操作,获取设备的信息、改变设备的运行状态。
设备模型的结构组成:
1.总线。
所有的设备都通过总线相连,包括内部的虚拟总线。在内核中,用
struct bus_type结构体来表示总线。
struct bus_type { const char * name; struct kset drivers; struct kset devices; }
name是总线的名字。
kset drivers与
kset 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是驱动程序当前能操作的设备链表。
主要数据结构
struct net_device:表示网络设备实例,存储了网络设备的一些关键信息:
设备名称。
设备mac地址。由软件随机生成。
广播地址。
以太网类型、帧长度、帧头长度、mtu大小。
设备模型对象。
struct net_bridge:网桥的默认vid,默认优先级,生成树使能状态。
struct net_port_vlans:网桥管理的所有vlan列表。
struct net_bridge_fdb_entry:网桥管理的所有mac转发表。
netdev_queue,
netdev_rx_queue:网桥的收发包队列。
struct net:linux网络命名空间
struct net_port_vlans { u16 port_idx; u16 pvid; union { struct net_bridge_port *port; struct net_bridge *br; /* vlan所属网桥,即vlan对哪个网桥生效 */ } parent; struct rcu_head rcu; 1fff8 unsigned long vlan_bitmap[BR_VLAN_BITMAP_LEN]; /* 已创建的vlan列表 */ unsigned long untagged_bitmap[BR_VLAN_BITMAP_LEN]; /* 哪些vlan出口时 去掉tag */ u16 num_vlans; }; struct net_bridge_fdb_entry { struct hlist_node hlist; struct net_bridge_port *dst; /* mac地址绑定的端口 */ unsigned long updated; unsigned long used; mac_addr addr; /* mac地址 */ __u16 vlan_id; unsigned char is_local:1, /* 是否本机mac地址 */ is_static:1, /* 是否静态mac地址 */ added_by_user:1, added_by_external_learn:1; struct rcu_head rcu; }; struct net_bridge { spinlock_t lock; struct list_head port_list; /* 网桥的虚拟端口struct net_bridge_port */ struct net_device *dev; /* 网桥设备br0信息 */ struct pcpu_sw_netstats __percpu *stats; spinlock_t hash_lock; struct hlist_head hash[BR_HASH_SIZE]; /* mac地址hash表, 每个mac地址由struct net_bridge_fdb_entry表示 */ } struct net_device { char name[IFNAMSIZ]; /* 设备名称 */ struct hlist_node name_hlist; /* 设备名称链表 */ struct netdev_hw_addr_list dev_addrs; /* 设备mac地址 */ struct hlist_node index_hlist; /* ifindex链表 */ unsigned long mem_end; /* 设备共享内存的起始地址 */ unsigned long mem_start; /* 设备共享内存的结束地址 */ unsigned long base_addr; /* 网络设备I/O基地址 */ int irq; /* 设备使用的中断号 */ const struct net_device_ops *netdev_ops; const struct ethtool_ops *ethtool_ops; const struct forwarding_accel_ops *fwd_ops; }
主要链表结构
主要使用的链表数据结构是hlist_head与
hlist_node,其工作原理与
list_head类似。
struct hlist_head { struct hlist_node *first; }; struct hlist_node { struct hlist_node *next, **pprev; };
通过
hlist_add_head_rcu(struct hlist_node *n, struct hlist_head *h)函数,可以把新节点添加到链表中。
空节点
添加第一个节点
添加第二个节点
platform虚拟总线、设备、驱动
struct kobject与kset结构体
kobject与kset是组成设备模型的基本结构,为来表示设备模型的设备实例与设备层次关系。这两个结构体作为面向对象概念的基类,嵌套在其他结构体里使用。在sysfs中显示的每一个实例,都对应一个kobject。/* 设备模型的基本结构。 * 在sysfs中显示的每一个对象,都对应一个kobject,它被用来与内核交互并创建它的可见表述。 * 内核用kobject结构将各个对象连接起来组成一个分层的结构体系 */ struct kobject { const char *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下以kobj.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()
devices_init()创建sysfs模型如下:
“`c
/sys
├── dev
│ ├── block
│ └── char
└── devices
```c struct kset *devices_kset; /* /sys/devices/ */ static struct kobject *dev_kobj; struct kobject *sysfs_dev_char_kobj; struct kobject *sysfs_dev_block_kobj; int __init devices_init(void) { devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL); dev_kobj = kobject_create_and_add("dev", NULL); sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj); sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj); } <div class="se-preview-section-delimiter"></div>
kset_create_and_add()
/* 动态创建kset结构体,在sysfs中创建以@name命名的目录,如果kset->kobj.ktype有属性值, 则在目录下创建以属性名命名的文件 */ struct kset *kset_create_and_add(const char *name, const struct kset_uevent_ops *uevent_ops, struct kobject *parent_kobj) { struct kset *kset; kset = kset_create(name, uevent_ops, parent_kobj); /* 动态创建kset结构体 */ kset_register(kset); /* 初始化kset,并加入到sysfs */ } /* 动态创建kset结构体 */ static struct kset *kset_create(const char *name, const struct kset_uevent_ops *uevent_ops, struct kobject *parent_kobj) { struct kset *kset; int retval; kset = kzalloc(sizeof(*kset), GFP_KERNEL); kobject_set_name(&kset->kobj, "%s", name); kset->uevent_ops = uevent_ops; kset->kobj.parent = parent_kobj; kset->kobj.ktype = &kset_ktype; kset->kobj.kset = NULL; return kset; } /* 初始化kset,并加入到sysfs */ int kset_register(struct kset * k) { kset_init(k); kobject_add_internal(&k->kobj); kobject_uevent(&k->kobj, KOBJ_ADD); } void kset_init(struct kset * k) { kobject_init_internal(&k->kobj); INIT_LIST_HEAD(&k->list); spin_lock_init(&k->list_lock); } static void kobject_init_internal(struct kobject *kobj) { kref_init(&kobj->kref); /* 初始化kobject的引用计数为1 */ INIT_LIST_HEAD(&kobj->entry); /* 初始化entry链表 */ kobj->state_in_sysfs = 0; /* 设备注册标志,0表示未注册 */ kobj->state_add_uevent_sent = 0; kobj->state_remove_uevent_sent = 0; kobj->state_initialized = 1; } /* kobject_add_internal()在sysfs下建立以kobj.name命名的目录 */ static int kobject_add_internal(struct kobject *kobj) { struct kobject *parent; parent = kobject_get(kobj->parent); /* join kset if set, use it as parent if we do not already have one */ if (kobj->kset) { if (!parent) parent = kobject_get(&kobj->kset->kobj); kobj_kset_join(kobj); kobj->parent = parent; } create_dir(kobj); kobj->state_in_sysfs = 1; /* 标记设备已注册 */ } /* 创建kobject对象的目录及属性文件 */ static int create_dir(struct kobject *kobj) { const struct kobj_ns_type_operations *ops; sysfs_create_dir_ns(kobj, kobject_namespace(kobj)); /* 创建目录 */ populate_dir(kobj); /* 创建属性文件 */ sysfs_get(kobj->sd); ops = kobj_child_ns_ops(kobj); if (ops) { sysfs_enable_ns(kobj->sd); } } /* 以kobj.name命名,在sysfs下创建一个目录 */ int sysfs_create_dir_ns(struct kobject *kobj, const void *ns) { struct kernfs_node *parent, *kn; /* 指定创建的kobj目录所属的的父目录,默认为sysfs根目录 */ if (kobj->parent) parent = kobj->parent->sd; else parent = sysfs_root_kn; /* 在parent目录下,创建以kobj.name命名的目录 */ kn = kernfs_create_dir_ns(parent, kobject_name(kobj), S_IRWXU | S_IRUGO | S_IXUGO, kobj, ns); kobj->sd = kn; } /* 对象可能是带有属性的,在kobject目录下创建以属性名命名的文件 */ static int populate_dir(struct kobject *kobj) { struct kobj_type *t = get_ktype(kobj); struct attribute *attr; int i; if (t && t->default_attrs) { for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) { sysfs_create_file(kobj, attr); } } } <div class="se-preview-section-delimiter"></div>
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL)创建的模型:
kobject_create_and_add()
/* 动态创建一个kobject对象,并且加入到sysfs */ struct kobject *kobject_create_and_add(const char *name, struct kobject *parent) { struct kobject *kobj; kobj = kobject_create(); kobject_add(kobj, parent, "%s", name); return kobj; } struct kobject *kobject_create(void) { struct kobject *kobj; kobj = kzalloc(sizeof(*kobj), GFP_KERNEL); kobject_init(kobj, &dynamic_kobj_ktype); return kobj; } /* 初始化kobject结构体 */ void kobject_init(struct kobject *kobj, struct kobj_type *ktype) { kobject_init_internal(kobj); kobj->ktype = ktype; } int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...) { va_list args; va_start(args, fmt); kobject_add_varg(kobj, parent, fmt, args); va_end(args); } static int kobject_add_varg(struct kobject *kobj, struct kobject *parent, const char *fmt, va_list vargs) { kobject_set_name_vargs(kobj, fmt, vargs); kobj->parent = parent; return kobject_add_internal(kobj); } /* 设置@kobj的name */ int kobject_set_name_vargs(struct kobject *kobj, const char *fmt, va_list vargs) { char *s; kobj->name = kvasprintf(GFP_KERNEL, fmt, vargs); /* 名字中不能包含有'/'的字符,如果有,则直接截短 */ while ((s = strchr(kobj->name, '/'))) s[0] = '!'; } <div class="se-preview-section-delimiter"></div>
dev_kobj = kobject_create_and_add("dev", NULL)创建的模型:
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj)与
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj)创建的模型
buses_init()
buses_init()创建sysfs模型如下:
“`c
/sys
├── bus
└── devices
└── system
<div class="se-preview-section-delimiter"></div> ```c static struct kset *bus_kset; static struct kset *system_kset; /* /sys/devices/system */ int __init buses_init(void) { bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL); system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj); }
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL)创建的模型:
system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj)创建的模型:
platform_bus_init()
platform_bus_init()完成虚拟设备platform,及虚拟总线platform的创建。
“`c
/sys
└── devices
└── platform
└── uevent /* -rw-r–r– */
```c /* platform设备 */ struct device platform_bus = { .init_name = "platform", }; /* platform总线 */ struct bus_type platform_bus_type = { .name = "platform", .dev_groups = platform_dev_groups, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops, }; int __init platform_bus_init(void) { early_platform_cleanup(); device_register(&platform_bus); /* 注册到devices目录中 */ bus_register(&platform_bus_type); /* 注册到bus目录中 */ of_platform_register_reconfig_notifier(); } <div class="se-preview-section-delimiter"></div>
device_register()
/* 注册设备:初始化设备的数据结构,将其加入到数据结构的网络中。 完成设备注册后,可以在/sys/devices目录中看到 */ int device_register(struct device *dev) { device_initialize(dev); /* 初始化dev结构 */ device_add(dev); /* 添加dev至sysfs */ } void device_initialize(struct device *dev) { /* 将设备kobject的kset集合指向devices_kset */ dev->kobj.kset = devices_kset; kobject_init(&dev->kobj, &device_ktype); } <div class="se-preview-section-delimiter"></div> #define DEVICE_ATTR_RW(_name) \ struct device_attribute dev_attr_##_name = __ATTR_RW(_name) <div class="se-preview-section-delimiter"></div> #define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO), \ _name##_show, _name##_store) <div class="se-preview-section-delimiter"></div> #define __ATTR(_name, _mode, _show, _store) { \ .attr = {.name = __stringify(_name), \ .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \ .show = _show, \ .store = _store, \ } static DEVICE_ATTR_RW(uevent); /* 定义dev_attr_uevent,其展开宏如下: struct device_attribute dev_attr_uevent { .attr = { .name = "uevent", .mode = (S_IWUSR | S_IRUGO), }, .show = uevent_show, .store = uevent_store, }; */ int device_add(struct device *dev) { struct device *parent = NULL; struct kobject *kobj; struct class_interface *class_intf; /* 将设备的引用计数加1 */ dev = get_device(dev); /* 创建私有数据结构体,并与主结构体进行关联 */ if (!dev->p) { device_private_init(dev); } /* 使用dev.init_name设置dev.kobj.name,并置空dev.init_name */ if (dev->init_name) { dev_set_name(dev, "%s", dev->init_name); dev->init_name = NULL; } /* 如果kobject_add()的第二个参数parent为空,则以dev->kobj.kset.kobj作为父目录 */ kobject_add(&dev->kobj, dev->kobj.parent, NULL); /* dev_attr_uevent由宏DEVICE_ATTR_RW()定义,在dev.kobj目录下创建以 dev_attr_uevent.attr.name命名,权限为dev_attr_uevent.attr.mode的文件 */ device_create_file(dev, &dev_attr_uevent); /* 创建设备类型软链接 */ device_add_class_symlinks(dev); bus_add_device(dev); } int bus_add_device(struct device *dev) { struct bus_type *bus = bus_get(dev->bus); if (bus) { device_add_attrs(bus, dev); device_add_groups(dev, bus->dev_groups); sysfs_create_link(&bus->p->devices_kset->kobj, &dev->kobj, dev_name(dev)); sysfs_create_link(&dev->kobj, &dev->bus->p->subsys.kobj, "subsystem"); klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices); } } <div class="se-preview-section-delimiter"></div>
bus_register()
bus_register(&platform_bus_type)向系统添加一个新总线。
/sys └── bus └── platform ├── devices ├── drivers ├── drivers_autoprobe /* -rw-r--r-- */ ├── drivers_probe /* --w------- */ └── uevent /* --w------- */
struct bus_type { const char * name; /* 总线的文本名称,用于在sysfs文件系统中标识总线 */ /* 试图查找与给定设备匹配的驱动程序 */ 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 subsys_private *p; }; struct bus_type platform_bus_type = { .name = "platform", .dev_groups = platform_dev_groups, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops, }; static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store); /* 定义bus_attr_uevent ,其展开宏如下: struct bus_attribute bus_attr_uevent { .attr = { .name = "uevent", .mode = S_IWUSR, }, .show = NULL, .store = bus_uevent_store, }; */ /* 注册新的总线 */ 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); priv->bus = bus; bus->p = priv; /* */ kobject_set_name(&priv->subsys.kobj, "%s", bus->name); priv->subsys.kobj.kset = bus_kset; /* 父目录指向bus_kset.kobj */ priv->subsys.kobj.ktype = &bus_ktype; priv->drivers_autoprobe = 1; /* 初始化&priv->subsys,并加入到sysfs */ kset_register(&priv->subsys); /* bus_attr_uevent由宏BUS_ATTR()定义,在priv.subsys.kobj目录下创建以 bus_attr_uevent.attr.name命名,权限为bus_attr_uevent.attr.mode的文件 */ bus_create_file(bus, &bus_attr_uevent); priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj); priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj); add_probe_files(bus); /* 创建drivers_probe与drivers_autoprobe文件 */ bus_add_groups(bus, bus->bus_groups); }
内核创建的基础设备模型框架
driver_init()最终形成的模型框架,如下所示:
/sys ├── bus │ └── platform │ ├── devices │ ├── drivers │ ├── drivers_autoprobe /* -rw-r--r-- */ │ ├── drivers_probe /* --w------- */ │ └── uevent /* --w------- */ ├── dev │ ├── block │ └── char └── devices └── platform └── uevent /* -rw-r--r-- */
AR9331硬件架构
系统框图
功能框图
AR9331支持4个集成PHY的LAN口,及1个集成PHY的WAN口。4个LAN口通过GMII与CPU连接,WAN口可以配置使用指定的MII接口与CPU连接。
地址映射
交换控制器
AR9331以太网交换控制器包含5个10/100M FE端口。
AR9331集成2个GE MAC。交换芯片的phy4 WAN口通过GEO的MII接口与CPU连接。
PHY0 ~ PHY4支持桥接模式相连,在桥接模式下,GE0必须处于复位模式,所有5个口都 作为LAN口,并通过CPU的GE1与MAC0连接。如果GE0单独连接到PHY,MAC5必须处于复位模式。
代码分析
内核初始化
通过宏MIPS_MACHINE()定义了
struct mips_machine machine_ATH79_MACH_TL_WR741ND_V4_type结构体,挂接了配置函数
tl_wr741ndv4_setup()。宏
MIPS_MACHINE()的作用是把定义的结构体置于
.mips.machines.init段中。
当用户配置用Kernel command line的参数
board=TL-WR741ND-v4,内核函数
__setup("machtype=", mips_machtype_setup)设置设备类型
mips_machtype = TL-WR741ND-v4。
内核初始化时,根据vmlinux.lds的段安排,内核初始化函数
mips_machine_setup()会遍历
.mips.machines.init段中的所有
struct mips_machine结构体成员,找出设备类型为“TL-WR741ND-v4”的成员,并设置设备名称
mips_machine_name = "machine_name_ATH79_MACH_TL_WR741ND_V4",最后调用
tl_wr741ndv4_setup()对设备进行初始化配置。
MIPS_MACHINE(ATH79_MACH_TL_WR741ND_V4, "TL-WR741ND-v4", "TP-LINK TL-WR741ND v4", tl_wr741ndv4_setup); #define MIPS_MACHINE(_type, _id, _name, _setup) \ static const char machine_name_##_type[] __initconst \ __aligned(1) = _name; \ static const char machine_id_##_type[] __initconst \ __aligned(1) = _id; \ static struct mips_machine machine_##_type \ __used __section(.mips.machines.init) = \ { \ .mach_type = _type, \ .mach_id = machine_id_##_type, \ .mach_name = machine_name_##_type, \ .mach_setup = _setup, \ }; /* vmlinux.lds */ .mips.machines.init : AT(ADDR(.mips.machines.init) - 0) { __mips_machines_start = .; *(.mips.machines.init) __mips_machines_end = .;
注册platform设备
platform设备注册的接口是platform_device_register()。
AR9331芯片寄存器描述:
#define AR71XX_APB_BASE 0x18000000 #define AR933X_GMAC_BASE (AR71XX_APB_BASE + 0x00070000) /* * AR933X GMAC interface */ #define AR933X_GMAC_REG_ETH_CFG 0x00 #define AR933X_ETH_CFG_SW_PHY_SWAP BIT(7) #define AR933X_ETH_CFG_SW_PHY_ADDR_SWAP BIT(8)
static void __init tl_wr741ndv4_setup(void) { u8 *mac = (u8 *) KSEG1ADDR(0x1f01fc00); u8 *ee = (u8 *) KSEG1ADDR(0x1fff1000); /* 寄存器ETH_CFG的第7、第8位,置位 */ ath79_setup_ar933x_phy4_switch(true, true); /* 清除寄存器GPIO_FUNCTION_1的第3~7位 */ ath79_gpio_function_disable(AR933X_GPIO_FUNC_ETH_SWITCH_LED0_EN | AR933X_GPIO_FUNC_ETH_SWITCH_LED1_EN | AR933X_GPIO_FUNC_ETH_SWITCH_LED2_EN | AR933X_GPIO_FUNC_ETH_SWITCH_LED3_EN | AR933X_GPIO_FUNC_ETH_SWITCH_LED4_EN); ath79_register_leds_gpio(-1, ARRAY_SIZE(tl_wr741ndv4_leds_gpio), tl_wr741ndv4_leds_gpio); ath79_register_gpio_keys_polled(1, TL_WR741NDV4_KEYS_POLL_INTERVAL, ARRAY_SIZE(tl_wr741ndv4_gpio_keys), tl_wr741ndv4_gpio_keys); ath79_register_m25p80(&tl_wr741ndv4_flash_data); /* 设置eth0与eth1的mac地址,分别是mac+1与mac-1 */ ath79_init_mac(ath79_eth0_data.mac_addr, mac, 1); ath79_init_mac(ath79_eth1_data.mac_addr, mac, -1); /* 挂接mdio驱动,注册mdio设备。 mdio_dev = &ath79_mdio1_device; mdio_data = &ath79_mdio1_data; 且mdio_data->builtin_switch置位 */ ath79_register_mdio(0, 0x0); /* 注册eth设备 */ ath79_register_eth(1); ath79_register_eth(0); /* 注册wmac设备 */ ath79_register_wmac(ee, mac); } /* ath79_setup_ar933x_phy4_switch()`设置`ETH_CFG`寄存器的第7、第8位 */ void __init ath79_setup_ar933x_phy4_switch(bool mac, bool mdio) { void __iomem *base; u32 t; base = ioremap(AR933X_GMAC_BASE, AR933X_GMAC_SIZE); t = __raw_readl(base + AR933X_GMAC_REG_ETH_CFG); t &= ~(AR933X_ETH_CFG_SW_PHY_SWAP | AR933X_ETH_CFG_SW_PHY_ADDR_SWAP); if (mac) t |= AR933X_ETH_CFG_SW_PHY_SWAP; if (mdio) t |= AR933X_ETH_CFG_SW_PHY_ADDR_SWAP; __raw_writel(t, base + AR933X_GMAC_REG_ETH_CFG); iounmap(base); }
ath79_init_mac()设置mac地址。
void __init ath79_init_mac(unsigned char *dst, const unsigned char *src, int offset) { int t; if (!dst) return; if (!src || !is_valid_ether_addr(src)) { memset(dst, '\0', ETH_ALEN); return; } t = (((u32) src[3]) << 16) + (((u32) src[4]) << 8) + ((u32) src[5]); t += offset; dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = (t >> 16) & 0xff; dst[4] = (t >> 8) & 0xff; dst[5] = t & 0xff; }
ath79_register_eth()注册eth设备。
struct platform_device ath79_eth0_device = { .name = "ag71xx", .id = 0, .resource = ath79_eth0_resources, .num_resources = ARRAY_SIZE(ath79_eth0_resources), .dev = { .platform_data = &ath79_eth0_data, }, }; void __init ath79_register_eth(unsigned int id) { struct platform_device *pdev; struct ag71xx_platform_data *pdata; int err; if (id > 1) { printk(KERN_ERR "ar71xx: invalid ethernet id %d\n", id); return; } /* 初始化ath79_ethX_pll_data */ ath79_init_eth_pll_data(id); if (id == 0) pdev = &ath79_eth0_device; else pdev = &ath79_eth1_device; /* 设置ath79_eth0_device.dev.platform_data.phy_if_mode = PHY_INTERFACE_MODE_MII。 ath79_eth0_device.dev.platform_data.phy_if_mode = PHY_INTERFACE_MODE_GMII */ pdata = pdev->dev.platform_data; err = ath79_setup_phy_if_mode(id, pdata); if (err) { printk(KERN_ERR "ar71xx: invalid PHY interface mode for GE%u\n", id); return; } switch (ath79_soc) { case ATH79_SOC_AR9331: if (id == 0) { pdata->reset_bit = AR933X_RESET_GE0_MAC | AR933X_RESET_GE0_MDIO; pdata->ddr_flush = ar933x_ddr_flush_ge0; pdata->set_speed = ath79_set_speed_dummy; pdata->phy_mask = BIT(4); } else { pdata->reset_bit = AR933X_RESET_GE1_MAC | AR933X_RESET_GE1_MDIO; pdata->ddr_flush = ar933x_ddr_flush_ge1; pdata->set_speed = ath79_set_speed_dummy; pdata->speed = SPEED_1000; pdata->duplex = DUPLEX_FULL; pdata->switch_data = &ath79_switch_data; ath79_switch_data.phy_poll_mask |= BIT(4); } pdata->has_gbit = 1; pdata->is_ar724x = 1; if (!pdata->fifo_cfg1) pdata->fifo_cfg1 = 0x0010ffff; if (!pdata->fifo_cfg2) pdata->fifo_cfg2 = 0x015500aa; if (!pdata->fifo_cfg3) pdata->fifo_cfg3 = 0x01f00140; break; } switch (pdata->phy_if_mode) { case PHY_INTERFACE_MODE_GMII: case PHY_INTERFACE_MODE_RGMII: if (!pdata->has_gbit) { printk(KERN_ERR "ar71xx: no gbit available on eth%d\n", id); return; } /* fallthrough */ default: break; } if (!is_valid_ether_addr(pdata->mac_addr)) { random_ether_addr(pdata->mac_addr); printk(KERN_DEBUG "ar71xx: using random MAC address for eth%d\n", ath79_eth_instance); } if (pdata->mii_bus_dev == NULL) { switch (ath79_soc) { case ATH79_SOC_AR9331: pdata->mii_bus_dev = &ath79_mdio1_device.dev; break; } } /* 设置寄存器RST_RESET,重启MAC与MDIO,寄存器进入复位状态 */ ath79_device_reset_set(pdata->reset_bit); mdelay(100); /* 解除重启,进入正常工作 */ ath79_device_reset_clear(pdata->reset_bit); mdelay(100); /* 注册platform设备dev */ platform_device_register(pdev); ath79_eth_instance++; } int platform_device_register(struct platform_device *pdev) { /* pdev->dev->kobj.kset = devices_kset */ device_initialize(&pdev->dev); arch_setup_pdev_archdata(pdev); platform_device_add(pdev); } int platform_device_add(struct platform_device *pdev) { /* 关联platform设备框架 */ if (!pdev->dev.parent) pdev->dev.parent = &platform_bus; /* 关联platform总线框架 */ pdev->dev.bus = &platform_bus_type; /* 设置pdev->dev.kobj.name = "pdev->name"."pdev->id" */ dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); device_add(&pdev->dev); }
ath79_register_wmac()注册wmac设备。
void __init ath79_register_wmac(u8 *cal_data, u8 *mac_addr) { ar933x_wmac_setup(); if (cal_data) memcpy(ath79_wmac_data.eeprom_data, cal_data, sizeof(ath79_wmac_data.eeprom_data)); if (mac_addr) { memcpy(ath79_wmac_mac, mac_addr, sizeof(ath79_wmac_mac)); ath79_wmac_data.macaddr = ath79_wmac_mac; } /* 注册platform设备ath79_wmac_device */ platform_device_register(&ath79_wmac_device); } static void __init ar933x_wmac_setup(void) { u32 t; /* 设置寄存器RST_RESET.WLAN_RESET,使WLAN重启 */ ar933x_wmac_reset(); ath79_wmac_device.name = "ar933x_wmac"; /* 设置wmac的IORESOURCE_MEM为0x18100000 ~ 0x1811ffff MAC_PCU – 1810_8000 RTC – 1810_7000 Host Interface – 1810_4000 DCU – 1810_1000 QCU – 1810_0800 DMA – 1810_0000 wmac的IORESOURCE_IRQ = 2 */ 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; t = ath79_reset_rr(AR933X_RESET_REG_BOOTSTRAP); if (t & AR933X_BOOTSTRAP_REF_CLK_40) ath79_wmac_data.is_clk_25mhz = false; else ath79_wmac_data.is_clk_25mhz = true; if (ath79_soc_rev == 1) ath79_wmac_data.get_mac_revision = ar933x_r1_get_wmac_revision; ath79_wmac_data.external_reset = ar933x_wmac_reset; }
注册platform驱动
platform驱动注册的接口是platform_driver_register()。
static struct platform_driver ag71xx_driver = { .probe = ag71xx_probe, .remove = __exit_p(ag71xx_remove), .driver = { .name = "ag71xx", } }; static int __init ag71xx_module_init(void) { platform_driver_register(&ag71xx_driver); } #define platform_driver_register(drv) \ __platform_driver_register(drv, THIS_MODULE) int __platform_driver_register(struct platform_driver *drv, struct module *owner) { drv->driver.owner = owner; drv->driver.bus = &platform_bus_type; /* 如果驱动对象存在probe()等函数,则设置驱动对象的驱动成员 (struct platform_driver.driver)的probe等指针。 这样包装的目的:当内核设备驱动框架进行驱动挂接时,先调用platform_drv_probe()等 函数,这些函数会做一些额外的检查与保护,然后调用到实际的驱动probe()函数 */ if (drv->probe) drv->driver.probe = platform_drv_probe; if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; driver_register(&drv->driver); } int driver_register(struct device_driver *drv) { /* 查找驱动是否已注册 */ driver_find(drv->name, drv->bus); /* */ bus_add_driver(drv); driver_add_groups(drv, drv->groups); kobject_uevent(&drv->p->kobj, KOBJ_ADD); } /* 从platform总线框架里查找驱动名称是否已经注册。查找方式是查找priv.drivers_kset 保存的已注册驱动链表 */ struct device_driver *driver_find(const char *name, struct bus_type *bus) { struct kobject *k = kset_find_obj(bus->p->drivers_kset, name); struct driver_private *priv; if (k) { /* Drop reference added by kset_find_obj() */ kobject_put(k); priv = to_driver(k); return priv->driver; } return NULL; } int bus_add_driver(struct device_driver *drv) { struct bus_type *bus; struct driver_private *priv; int error = 0; priv = kzalloc(sizeof(*priv), GFP_KERNEL); klist_init(&priv->klist_devices, NULL, NULL); /* 关联私有数据结构体 */ priv->driver = drv; drv->p = priv; priv->kobj.kset = bus->p->drivers_kset; /* 设置priv.kobj.name */ kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name); /* 把新的驱动节点添加到总线的驱动维护链表里 */ klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); /* 如果设置了自动探测标志,则进行设备的探测与驱动的挂接加载。如果设备在驱动之前注册 的,那么就可以探测到;反之亦然,在注册设备时,也会执行这样的类似操作,进行驱动的 探测。所以,不管是先注册设备,还是先注册驱动,在注册时部是会进行探测匹配 */ if (drv->bus->p->drivers_autoprobe) { driver_attach(drv); } module_add_driver(drv->owner, drv); error = driver_create_file(drv, &driver_attr_uevent); if (error) { printk(KERN_ERR "%s: uevent attr (%s) failed\n", __func__, drv->name); } error = driver_add_groups(drv, bus->drv_groups); if (error) { /* How the hell do we get out of this pickle? Give up */ printk(KERN_ERR "%s: driver_create_groups(%s) failed\n", __func__, drv->name); } if (!drv->suppress_bind_attrs) { error = add_bind_files(drv); if (error) { /* Ditto */ printk(KERN_ERR "%s: add_bind_files(%s) failed\n", __func__, drv->name); } } return 0; out_unregister: kobject_put(&priv->kobj); kfree(drv->p); drv->p = NULL; out_put_bus: bus_put(bus); return error; } /* 探测设备,挂接驱动 */ 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; /* 把i->i_klist指向总线的设备维护链表klist_devices */ klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL)); /* 迭代链表klist_devices里的所有节点,执行__driver_attach()函数。 dev指向结构体struct platform_device.dev,本例中是设备ath79_eth0_device.dev。 data指向结构体struct platform_driver.device_driver,本例子中是 驱动ag71xx_driver.driver。 */ while ((dev = next_device(&i)) && !error) error = fn(dev, data); klist_iter_exit(&i); } static int __driver_attach(struct device *dev, void *data) { struct device_driver *drv = data; /* 执行platform_bus_type.match() */ if (!driver_match_device(drv, dev)) return 0; if (dev->parent) /* Needed for USB */ device_lock(dev->parent); device_lock(dev); /* 如果设备还未挂接过驱动,现在挂接驱动 */ if (!dev->driver) driver_probe_device(drv, dev); device_unlock(dev); if (dev->parent) device_unlock(dev->parent); return 0; } static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev = to_platform_device(dev); struct platform_driver *pdrv = to_platform_driver(drv); /* When driver_override is set, only bind to the matching driver */ if (pdev->driver_override) return !strcmp(pdev->driver_override, drv->name); /* Attempt an OF style match first */ if (of_driver_match_device(dev, drv)) return 1; /* Then try ACPI style match */ if (acpi_driver_match_device(dev, drv)) return 1; /* Then try to match against the id table */ if (pdrv->id_table) return platform_match_id(pdrv->id_table, pdev) != NULL; /* 对于本例,以上匹配规则都不会被执行。 设备名与驱动名都是"ag71xx",因此返回匹配。这里的设备名不是指在/sys/下看到的设备 名,/sys/下的设备名称是"ag71xx.0" */ return (strcmp(pdev->name, drv->name) == 0); } int driver_probe_device(struct device_driver *drv, struct device *dev) { int ret = 0; /* 判断设备是否已经注册。state_in_sysfs标志 */ if (!device_is_registered(dev)) return -ENODEV; pm_runtime_barrier(dev); /* 执行探测函数 */ really_probe(dev, drv); pm_request_idle(dev); } static int really_probe(struct device *dev, struct device_driver *drv) { /* 挂接驱动,ath79_eth0_device.dev.driver = &ag71xx_driver.driver */ dev->driver = drv; /* 设置用户空间sysfs的驱动与设备的挂接软链接 */ driver_sysfs_add(dev); if (dev->bus->probe) { dev->bus->probe(dev); } else if (drv->probe) { /* 执行这里 */ /* 执行ag71xx_driver.driver.probe(),即platform_drv_probe()。 platform_drv_probe()会做一些检查与保护,并调用到真正的驱动probe()函数, ag71xx_driver.probe(),即ag71xx_probe() */ drv->probe(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; done: atomic_dec(&probe_count); wake_up(&probe_waitqueue); return ret; } /* 创建软链接。sysfs是分层结构组织的,设备放在设备目录下,驱动放在驱动目录下,那么在用户 空间设备目录与驱动目录是如何挂接呢?技巧就是使用软链接,在设备目录软链接指向对应的驱动 ,在驱动目录下软链接指向对应的设备。 */ static int driver_sysfs_add(struct device *dev) { int ret; /* 这个是在创建软件链接,在dev->driver->p->kobj(即设备所挂接的驱动的私有数据结构 体的kobj)对象下创建以设备名称(即ath79_eth0_device.dev.kobj.name)命名的软链 接,指向设备的sysfs目录: /sys/bus/platform/drivers/ag71xx/ag71xx.0 -> /sys/devices/platform/ag71xx.0 */ ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj, kobject_name(&dev->kobj)); if (ret == 0) { /* 创建软链接: /sys/devices/platform/ag71xx.0/driver -> /sys/bus/platform/drivers/ag71xx/ */ sysfs_create_link(&dev->kobj, &dev->driver->p->kobj, "driver"); } }
struct net
linux每个进程有自己的命名空间,在clone进程时,为新进程分配命名空间。命名空间由全局变量
init_nsproxy定义,按功能模块进行独立管理,内核可配置选择是否使用网络命名空间用来管理所有的网络设备与驱动,初始化入口是
net_dev_init()。如果不使用命名空间,则每个新进程分配的命名空间都是使用父进程的命名空间,即所有进程都属于同一命名空间,网络命名空间是全局变量
init_net;如果使用命名空间,则每个新进程分配的命名空间是重新创建的命名空间,由链表
net_namespace_list管理所有的网络命名空间,第一个进程的网络命名空间仍然是
init_net。
static int __init net_dev_init(void) { dev_proc_init(); /* 在procfs下创建管理节点 */ netdev_kobject_init(); /* 赋值kobj_ns_ops_tbl[KOBJ_NS_TYPE_NET] = &net_ns_type_operations。 创建/sys/class/net/。 */ INIT_LIST_HEAD(&ptype_all); for (i = 0; i < PTYPE_HASH_SIZE; i++) INIT_LIST_HEAD(&ptype_base[i]); INIT_LIST_HEAD(&offload_base); if (register_pernet_subsys(&netdev_net_ops)) goto out; dev_boot_phase = 0; /* 把first_device指针指向loopback_net_ops.list */ register_pernet_device(&loopback_net_ops); /* loopback_net_ops(&init_net) */ register_pernet_device(&default_device_ops); /* 设置软中断softirq_vec[].action */ open_softirq(NET_TX_SOFTIRQ, net_tx_action); open_softirq(NET_RX_SOFTIRQ, net_rx_action); hotcpu_notifier(dev_cpu_callback, 0); dst_init(); /* 注册网络通告br_init */ } static int __net_init netdev_init(struct net *net) { /* init_net->dev_base_head在定义变量时就已经初始化了。 dev_base_head用来把所有设备的struct net_device链接起来。 */ if (net != &init_net) INIT_LIST_HEAD(&net->dev_base_head); /* 分配256个struct hlist_node大小的空间 */ net->dev_name_head = netdev_create_hash(); /* 分配256个struct hlist_node大小的空间 */ net->dev_index_head = netdev_create_hash(); } static struct pernet_operations __net_initdata netdev_net_ops = { .init = netdev_init, .exit = netdev_exit, }; int register_pernet_subsys(struct pernet_operations *ops) { register_pernet_operations(first_device, ops); } static int register_pernet_operations(struct list_head *list, struct pernet_operations *ops) { __register_pernet_operations(list, ops); } /* 没有网络命名空间的情况下 */ static int __register_pernet_operations(struct list_head *list, struct pernet_operations *ops) { return ops_init(ops, &init_net); } static int ops_init(const struct pernet_operations *ops, struct net *net) { ops->init(net); } static struct pernet_operations __net_initdata dev_proc_ops = { .init = dev_proc_net_init, .exit = dev_proc_net_exit, }; static struct pernet_operations __net_initdata dev_mc_net_ops = { .init = dev_mc_net_init, .exit = dev_mc_net_exit, }; int __init dev_proc_init(void) { register_pernet_subsys(&dev_proc_ops); /* 执行dev_proc_net_init(&init_net) */ register_pernet_subsys(&dev_mc_net_ops); /* 执行dev_mc_net_init(&init_net) */ } int __init netdev_kobject_init(void) { /* 赋值kobj_ns_ops_tbl[KOBJ_NS_TYPE_NET] = &net_ns_type_operations */ kobj_ns_type_register(&net_ns_type_operations); /* 创建/sys/class/net/ */ return class_register(&net_class); } ```c **驱动探测** <div class="se-preview-section-delimiter"></div> ```c static int __devinit ag71xx_probe(struct platform_device *pdev) { struct net_device *dev; struct resource *res; struct ag71xx *ag; struct ag71xx_platform_data *pdata; int err; /* ath79_eth0_data */ pdata = pdev->dev.platform_data; /* 设备注册时已经初始化,pdata->mii_bus_dev = &ath79_mdio1_device.dev */ if (pdata->mii_bus_dev == NULL) { dev_err(&pdev->dev, "no MII bus device specified\n"); err = -EINVAL; goto err_out; } /* 创建eth设备的net_device */ dev = alloc_etherdev(sizeof(*ag)); SET_NETDEV_DEV(dev, &pdev->dev); ag = netdev_priv(dev); ag->pdev = pdev; ag->dev = dev; ag->msg_enable = netif_msg_init(ag71xx_msg_level, AG71XX_DEFAULT_MSG_ENABLE); spin_lock_init(&ag->lock); res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mac_base"); ag->mac_base = ioremap_nocache(res->start, res->end - res->start + 1); dev->irq = platform_get_irq(pdev, 0); err = request_irq(dev->irq, ag71xx_interrupt, IRQF_DISABLED, dev->name, dev); dev->base_addr = (unsigned long)ag->mac_base; dev->netdev_ops = &ag71xx_netdev_ops; dev->ethtool_ops = &ag71xx_ethtool_ops; INIT_WORK(&ag->restart_work, ag71xx_restart_work_func); init_timer(&ag->oom_timer); ag->oom_timer.data = (unsigned long) dev; ag->oom_timer.function = ag71xx_oom_timer_handler; ag->tx_ring.size = AG71XX_TX_RING_SIZE_DEFAULT; ag->rx_ring.size = AG71XX_RX_RING_SIZE_DEFAULT; ag->stop_desc = dma_alloc_coherent(NULL, sizeof(struct ag71xx_desc), &ag->stop_desc_dma, GFP_KERNEL); if (!ag->stop_desc) goto err_free_irq; ag->stop_desc->data = 0; ag->stop_desc->ctrl = 0; ag->stop_desc->next = (u32) ag->stop_desc_dma; memcpy(dev->dev_addr, pdata->mac_addr, ETH_ALEN); netif_napi_add(dev, &ag->napi, ag71xx_poll, AG71XX_NAPI_WEIGHT); register_netdev(dev); /* 打印Ethernet寄存器信息 */ ag71xx_dump_regs(ag); ag71xx_hw_init(ag); ag71xx_dump_regs(ag); ag71xx_phy_connect(ag); ag71xx_debugfs_init(ag); platform_set_drvdata(pdev, dev); return 0; }
分配网络设备实例并注册
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1) #define alloc_etherdev_mq(sizeof_priv, count) alloc_etherdev_mqs(sizeof_priv, count, count) struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs, unsigned int rxqs) { return alloc_netdev_mqs(sizeof_priv, "eth%d", NET_NAME_UNKNOWN, ether_setup, txqs, rxqs); } /* @sizeof_priv:私有数据,比如设备的特征信息等,用户可根据需要添加任意的结构体。 @setup():初始化设备的回调函数ether_setup() @txqs:TX队列的个数 @rxqs:RX队列的个数 */ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, unsigned char name_assign_type, void (*setup)(struct net_device *), unsigned int txqs, unsigned int rxqs) { struct net_device *dev; size_t alloc_size; struct net_device *p; /* 在连续内存空间分配两个结构体,net_device与struct ag71xx */ alloc_size = sizeof(struct net_device); if (sizeof_priv) { alloc_size += sizeof_priv; } dev = kzalloc(alloc_size, GFP_KERNEL | __GFP_NOWARN | __GFP_REPEAT); dev->pcpu_refcnt = alloc_percpu(int); dev_addr_init(dev); /* 初始化设备mac的mac地址链表 */ dev_mc_init(dev); /* 初始化多播转发mac地址链表 */ dev_uc_init(dev); /* 初始化单播转发mac地址链表 */ /* 关联命名空间,dev->nd_net = &init_net */ dev_net_set(dev, &init_net); /* 设置GSO */ dev->gso_max_size = GSO_MAX_SIZE; dev->gso_max_segs = GSO_MAX_SEGS; dev->gso_min_segs = 0; INIT_LIST_HEAD(&dev->napi_list); INIT_LIST_HEAD(&dev->unreg_list); INIT_LIST_HEAD(&dev->close_list); INIT_LIST_HEAD(&dev->link_watch_list); INIT_LIST_HEAD(&dev->adj_list.upper); INIT_LIST_HEAD(&dev->adj_list.lower); INIT_LIST_HEAD(&dev->all_adj_list.upper); INIT_LIST_HEAD(&dev->all_adj_list.lower); INIT_LIST_HEAD(&dev->ptype_all); INIT_LIST_HEAD(&dev->ptype_specific); dev->priv_flags = IFF_XMIT_DST_RELEASE | IFF_XMIT_DST_RELEASE_PERM; setup(dev); /* 执行ether_setup() */ dev->num_tx_queues = txqs; /* =1 */ dev->real_num_tx_queues = txqs; /* =1 */ /* netif_alloc_netdev_queues()创建并初始化发包队列,并把队列挂接到`dev`设备上 */ netif_alloc_netdev_queues(dev); #ifdef CONFIG_SYSFS dev->num_rx_queues = rxqs; /* =1 */ dev->real_num_rx_queues = rxqs; /* =1 */ /* netif_alloc_rx_queues()创建并初始化收包队列,并把队列挂接到`dev`设备上 */ netif_alloc_rx_queues(dev); #endif strcpy(dev->name, name); dev->name_assign_type = name_assign_type; dev->group = INIT_NETDEV_GROUP; if (!dev->ethtool_ops) dev->ethtool_ops = &default_ethtool_ops; return dev; } void ether_setup(struct net_device *dev) { dev->header_ops = ð_header_ops; dev->type = ARPHRD_ETHER; dev->hard_header_len = ETH_HLEN; dev->mtu = ETH_DATA_LEN; dev->addr_len = ETH_ALEN; dev->tx_queue_len = 1000; /* Ethernet wants good queues */ dev->flags = IFF_BROADCAST|IFF_MULTICAST; dev->priv_flags |= IFF_TX_SKB_SHARING; memset(dev->broadcast, 0xFF, ETH_ALEN); } /* 注册net_device设备 */ int register_netdev(struct net_device *dev) { rtnl_lock(); err = register_netdevice(dev); rtnl_unlock(); } int register_netdevice(struct net_device *dev) { int ret; struct net *net = dev_net(dev); BUG_ON(dev_boot_phase); /* net_dev_init()设置dev_boot_phase = 0 */ /* When net_device's are persistent, this will be fatal. */ BUG_ON(dev->reg_state != NETREG_UNINITIALIZED); BUG_ON(!net); spin_lock_init(&dev->addr_list_lock); netdev_set_addr_lockdep_class(dev); dev->iflink = -1; /* 如果名称是eth%d这种带有未决字符的,搜索未使用的索引进行赋值。 搜索方式:从命名空间的设备名链表struct net.dev_base_head里从0开始搜索 还未使用的最小索引值。 */ dev_get_valid_name(net, dev, dev->name); /* dev->netdev_ops = &ag71xx_netdev_ops */ if (dev->netdev_ops->ndo_init) { /* NULL,不执行 */ dev->netdev_ops->ndo_init(dev); } if (((dev->hw_features | dev->features) & NETIF_F_HW_VLAN_CTAG_FILTER) && (!dev->netdev_ops->ndo_vlan_rx_add_vid || !dev->netdev_ops->ndo_vlan_rx_kill_vid)) { netdev_WARN(dev, "Buggy VLAN acceleration in driver!\n"); ret = -EINVAL; goto err_uninit; } ret = -EBUSY; if (!dev->ifindex) dev->ifindex = dev_new_index(net); /* 分配一个ifindex,取net->ifindex的值 作为基值,与dev->index_hlist链表进行比较,如果已存在,则 把基值+1递增继续比较,直到找出未使用的ifindex则返回。 ifindex的值最小为1。 */ else if (__dev_get_by_index(net, dev->ifindex)) goto err_uninit; if (dev->iflink == -1) dev->iflink = dev->ifindex; /* Transfer changeable features to wanted_features and enable * software offloads (GSO and GRO). */ dev->hw_features |= NETIF_F_SOFT_FEATURES; dev->features |= NETIF_F_SOFT_FEATURES; dev->wanted_features = dev->features & dev->hw_features; if (!(dev->flags & IFF_LOOPBACK)) { dev->hw_features |= NETIF_F_NOCACHE_COPY; } /* Make NETIF_F_HIGHDMA inheritable to VLAN devices. */ dev->vlan_features |= NETIF_F_HIGHDMA; /* Make NETIF_F_SG inheritable to tunnel devices. */ dev->hw_enc_features |= NETIF_F_SG; /* Make NETIF_F_SG inheritable to MPLS. */ dev->mpls_features |= NETIF_F_SG; ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev); notifier_to_errno(ret); /* 初始化设备模型实例,并添加到/sys/devices/virtual/net/。 如果(!dev->parent && dev->class),则在virtual目录下创建class对应的目录,再 把设备加入到该目录下 */ netdev_register_kobject(dev); dev->reg_state = NETREG_REGISTERED; __netdev_update_features(dev); set_bit(__LINK_STATE_PRESENT, &dev->state); linkwatch_init_dev(dev); dev_init_scheduler(dev); dev_hold(dev); /* 把dev->name_hlist添加到net->dev_name_head, 把dev->ifindex添加到net->dev_index_head, 把dev->dev_list添加到net->dev_base_head */ list_netdevice(dev); add_device_randomness(dev->dev_addr, dev->addr_len); /* If the device has permanent device address, driver should * set dev_addr and also addr_assign_type should be set to * NET_ADDR_PERM (default value). */ if (dev->addr_assign_type == NET_ADDR_PERM) memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len); /* Notify protocols, that a new device appeared. */ ret = call_netdevice_notifiers(NETDEV_REGISTER, dev); ret = notifier_to_errno(ret); if (ret) { rollback_registered(dev); dev->reg_state = NETREG_UNREGISTERED; } /* * Prevent userspace races by waiting until the network * device is fully setup before sending notifications. */ if (!dev->rtnl_link_ops || dev->rtnl_link_state == RTNL_LINK_INITIALIZED) rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U, GFP_KERNEL); out: return ret; err_uninit: if (dev->netdev_ops->ndo_uninit) dev->netdev_ops->ndo_uninit(dev); goto out; }
硬件初始化
static void ag71xx_hw_init(struct ag71xx *ag) { struct ag71xx_platform_data *pdata = ag71xx_get_pdata(ag); u32 reset_mask = pdata->reset_bit; /* 关闭DMA中断与传输 */ ag71xx_hw_stop(ag); if (pdata->is_ar724x) { u32 reset_phy = reset_mask; reset_phy &= AR71XX_RESET_GE0_PHY | AR71XX_RESET_GE1_PHY; reset_mask &= ~(AR71XX_RESET_GE0_PHY | AR71XX_RESET_GE1_PHY); /* 复位phy */ ath79_device_reset_set(reset_phy); mdelay(50); /* 解复位phy */ ath79_device_reset_clear(reset_phy); mdelay(200); } /* 重启MAC所有模块 */ ag71xx_sb(ag, AG71XX_REG_MAC_CFG1, MAC_CFG1_SR); udelay(20); /* 复位PHY之外的所有模块 */ ath79_device_reset_set(reset_mask); mdelay(100); /* 解复位PHY之外的所有模块 */ ath79_device_reset_clear(reset_mask); mdelay(200); /* 使能MAC帧接收与转发功能、设置MTU */ ag71xx_hw_setup(ag); /* 配置并使能DMA */ ag71xx_dma_reset(ag); }
虚拟网桥
Linux下的Bridge也是一种虚拟设备,这多少和vlan有点相似,它依赖于一个或多个从设备。与VLAN不同的是,它不是虚拟出和从设备同一层次的镜像设备,而是虚拟出一个高一层次的设备,并把从设备虚拟化为端口port,且同时处理各个从设备的数据收发及转发,再加上netfilter框架的一些东西,使得它的实现相比vlan复杂得多。linux下配置网桥的命令:
brctl addbr br0 /* 创建虚拟网桥br0 */ brctl addif br0 eth0 /* 把物理设备eth0虚拟成网桥的端口 */ brctl addif br0 eth1 /* 把物理设备eth1虚拟成网桥的端口 */ ifconfig br0 192.168.1.1 /* 配置虚拟网桥设备的IP */
创建虚拟网桥
brctl addbr br0/* 用户空间brctl程序 */ /* @brname = "br0" */ int br_add_bridge(const char *brname) { ioctl(br_socket_fd, SIOCBRADDBR, brname); }
内核初始化时,注册了回调函数
br_ioctl_deviceless_stub(),用户空间的ioctl最终调用到该函数。
static int (*br_ioctl_hook) (struct net *, unsigned int cmd, void __user *arg); static int __init br_init(void) { /* 设置br_ioctl_hook = br_ioctl_deviceless_stub */ brioctl_set(br_ioctl_deviceless_stub); } static long sock_ioctl(struct file *file, unsigned cmd, unsigned long arg) { struct socket *sock; struct sock *sk; void __user *argp = (void __user *)arg; int pid, err; struct net *net; switch (cmd) { case SIOCBRADDBR: case SIOCBRDELBR: if (!br_ioctl_hook) request_module("bridge"); /* 加载模块 */ if (br_ioctl_hook) err = br_ioctl_hook(net, cmd, argp); /* br_ioctl_deviceless_stub() */ } } static const struct file_operations socket_file_ops = { .unlocked_ioctl = sock_ioctl, };
br_ioctl_deviceless_stub()根据用户空间的命令,执行相应的内核操作。如果是增加一个虚拟网桥,则调用
br_add_bridge()。
int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd, void __user *uarg) { switch (cmd) { case SIOCBRADDBR: case SIOCBRDELBR: { char buf[IFNAMSIZ]; if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) return -EPERM; if (copy_from_user(buf, uarg, IFNAMSIZ)) return -EFAULT; buf[IFNAMSIZ-1] = 0; if (cmd == SIOCBRADDBR) return br_add_bridge(net, buf); return br_del_bridge(net, buf); } } return -EOPNOTSUPP; }
br_add_bridge()完成网桥设备的动态创建、初始化,并添加到系统中。参数
@name就是用户空间命令brctl addbr br0的网桥名称“br0”。
int br_add_bridge(struct net *net, const char *name) { struct net_device *dev; int res; dev = alloc_netdev(sizeof(struct net_bridge), name, NET_NAME_UNKNOWN, br_dev_setup); dev_net_set(dev, net); dev->rtnl_link_ops = &br_link_ops; res = register_netdev(dev); return res; } #define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \ alloc_netdev_mqs(sizeof_priv, name, name_assign_type, setup, 1, 1)
br_dev_setup()初始化
net_bridge与
net_devcie结构体,以及虚拟网桥的组播、生成树协议的参数。
void br_dev_setup(struct net_device *dev) { struct net_bridge *br = netdev_priv(dev); /*因为net_device分配的内存是在 net_bridge后面,且是连续的内存空间,所以通过dev可以获得br的起始地址 */ eth_hw_addr_random(dev); /* dev.dev_addr设置成随机本地单播mac地址 */ ether_setup(dev); /* 设置以太网属性,包括mtu,包头长度,广播地址等等。 dev->priv_flags = IFF_XMIT_DST_RELEASE | IFF_XMIT_DST_RELEASE_PERM | IFF_TX_SKB_SHARING*/ dev->netdev_ops = &br_netdev_ops; /* 挂接网桥的驱动程序 */ dev->destructor = br_dev_free; dev->ethtool_ops = &br_ethtool_ops; SET_NETDEV_DEVTYPE(dev, &br_type); dev->priv_flags = IFF_EBRIDGE | IFF_NO_QUEUE; dev->features = COMMON_FEATURES | NETIF_F_LLTX | NETIF_F_NETNS_LOCAL | NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_STAG_TX; dev->hw_features = COMMON_FEATURES | NETIF_F_HW_VLAN_CTAG_TX | NETIF_F_HW_VLAN_STAG_TX; dev->vlan_features = COMMON_FEATURES; br->dev = dev; /* struct net_bridge的成员dev指向struct net_device */ spin_lock_init(&br->lock); INIT_LIST_HEAD(&br->port_list); spin_lock_init(&br->hash_lock); br->bridge_id.prio[0] = 0x80; br->bridge_id.prio[1] = 0x00; /* 设置组播地址 = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x00} */ ether_addr_copy(br->group_addr, eth_reserved_addr_base); /* 设置生成树参数 */ br->stp_enabled = BR_NO_STP; br->group_fwd_mask = BR_GROUPFWD_DEFAULT; br->group_fwd_mask_required = BR_GROUPFWD_DEFAULT; br->designated_root = br->bridge_id; br->bridge_max_age = br->max_age = 20 * HZ; br->bridge_hello_time = br->hello_time = 2 * HZ; br->bridge_forward_delay = br->forward_delay = 15 * HZ; br->ageing_time = BR_DEFAULT_AGEING_TIME; br_netfilter_rtable_init(br); /* netfilter */ br_stp_timer_init(br); /* 设备生成树定时器的回调函数 */ br_multicast_init(br); /* 设置组播参数,及组播定时器的回调函数 */ }
br_dev_init()初始化vid1,创建本机mac地址转发表。
static int br_dev_init(struct net_device *dev) { struct net_bridge *br = netdev_priv(dev); br_vlan_init(br); } int br_vlan_init(struct net_bridge *br) { br->vlan_proto = htons(ETH_P_8021Q); /* vlan协议标识符0x8100 */ br->default_pvid = 1; /* 默认pvid = 1 */ return br_vlan_add(br, 1, BRIDGE_VLAN_INFO_PVID | BRIDGE_VLAN_INFO_UNTAGGED); } int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags) { struct net_port_vlans *pv = NULL; pv = kzalloc(sizeof(*pv), GFP_KERNEL); pv->parent.br = br; /* 配置vlan所属网桥,即vlan对哪个网桥生效 */ __vlan_add(pv, vid, flags); } static int __vlan_add(struct net_port_vlans *v, u16 vid, u16 flags) { struct net_bridge_port *p = NULL; struct net_bridge *br; struct net_device *dev; int err; if (test_bit(vid, v->vlan_bitmap)) { /* 要添加的vlan,是否已经存在了 */ __vlan_add_flags(v, vid, flags); /* 如果vlan已经存在,则根据br_vlan_add() 的标志位参数,设置或删除pvid,设置或删除untagged_bitmap[] */ return 0; } /* 如果是新添加的vlan则继续往下执行 */ if (v->port_idx) { p = v->parent.port; br = p->br; dev = p->dev; } else { br = v->parent.br; dev = br->dev; } if (p) { /* Add VLAN to the device filter if it is supported. * This ensures tagged traffic enters the bridge when * promiscuous mode is disabled by br_manage_promisc(). */ vlan_vid_add(dev, br->vlan_proto, vid); } /* 添加设备的mac地址到桥转发表 */ br_fdb_insert(br, p, dev->dev_addr, vid); set_bit(vid, v->vlan_bitmap); v->num_vlans++; __vlan_add_flags(v, vid, flags); return 0; out_filt: if (p) vlan_vid_del(dev, br->vlan_proto, vid); return err; } int br_fdb_insert(struct net_bridge *br, struct net_bridge_port *source, const unsigned char *addr, u16 vid) { fdb_insert(br, source, addr, vid); } static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source, const unsigned char *addr, u16 vid) { /* 通过mac地址与vid组合,计算hash键值 */ struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)]; struct net_bridge_fdb_entry *fdb; /* 全0 mac地址,或者多播地址不应该加入转发表 */ if (!is_valid_ether_addr(addr)) return -EINVAL; fdb = fdb_find(head, addr, vid); /* br.hash[]表保存mac转发表,遍历此表,返回 匹配mac地址与vid的fdb结构体 */ /* 如果mac地址、vid组合已经存在,且属于本端口,则直接返回。 如果已经存在,但不属于本端口,说明发生了地址迁移,则刷新fdb:先删除fdb,再添加 */ if (fdb) { if (fdb->is_local) return 0; fdb_delete(br, fdb); } /* 如果fdb不存在要添加的mac+vid表项,则创建fdb,并添加到br->hash[]链表里。 这里创建了设备自身的mac+vid1表项。 */ fdb = fdb_create(head, source, addr, vid); fdb->is_local = fdb->is_static = 1; /* 把表项设置为本地、静态(不被老化) */ fdb_add_hw_addr(br, addr); fdb_notify(br, fdb, RTM_NEWNEIGH); /* 创建netlink消息并发送 */ return 0; }
图中
dev没有指向
br的指针,那怎么从
dev获取到
br呢?因为
dev与
br是同时申请的连续内存空间,所以通过
dev的指针+
dev的size,就可以获得
br的指针了。
添加网桥端口
brctl addif br0 eth0/* 用户空间brctl程序 */ /* @bridge = "br0", @dev = "eth0" */ int br_add_interface(const char *bridge, const char *dev) { struct ifreq ifr; int ifindex = if_nametoindex(dev); if (ifindex == 0) return ENODEV; strncpy(ifr.ifr_name, bridge, IFNAMSIZ); ifr.ifr_ifindex = ifindex; ioctl(br_socket_fd, SIOCBRADDIF, &ifr); }
创建的网桥设备“br0”已经挂接了驱动程序
br_netdev_ops,所以对“br0”进行添加端口从设备时,会调用到
br_netdev_ops->ndo_do_ioctl(),即
br_dev_ioctl()。
/* @rq.ifr_name = "br0", @rq.ifr_ifindex = "eth0转化的ifindex" */ int br_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { struct net_bridge *br = netdev_priv(dev); switch (cmd) { case SIOCBRADDIF: case SIOCBRDELIF: return add_del_if(br, rq->ifr_ifindex, cmd == SIOCBRADDIF); } } static int add_del_if(struct net_bridge *br, int ifindex, int isadd) { struct net *net = dev_net(br->dev); struct net_device *dev; int ret; if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) return -EPERM; /* 匹配@ifindex:在创建网桥时,网桥生成了一个ifindex,并通过list_netdevice()把 net->dev_index_head[].first指向dev.index_hlist,这样通过net就获取到了 dev.ifindex */ dev = __dev_get_by_index(net, ifindex); if (isadd) br_add_if(br, dev); else br_del_if(br, dev); } int br_add_if(struct net_bridge *br, struct net_device *dev) { if ((dev->flags & IFF_LOOPBACK) || dev->type != ARPHRD_ETHER || dev->addr_len != ETH_ALEN || !is_valid_ether_addr(dev->dev_addr) || netdev_uses_dsa(dev)) return -EINVAL; /* 虚拟网桥不能当做端口加入到另一个虚拟网桥中,即虚拟网桥不能桥接虚拟网桥,会出现回 环。因为虚拟网桥的dev->netdev_ops = &br_netdev_ops,所以可作为判断依据 */ if (dev->netdev_ops->ndo_start_xmit == br_dev_xmit) return -ELOOP; /* 判断设备是否已经加入到其他网桥里了,判断依据dev->priv_flags & IFF_BRIDGE_PORT */ if (br_port_exists(dev)) return -EBUSY; /* 某些设备是不能桥接的 */ if (dev->priv_flags & IFF_DONT_BRIDGE) return -EOPNOTSUPP; p = new_nbp(br, dev); /* 创建网桥端口数据并初始化 */ } static struct net_bridge_port *new_nbp(struct net_bridge *br, struct net_device *dev) { int index; struct net_bridge_port *p; index = find_portno(br); /* 获取最小可用端口号 */ if (index < 0) return ERR_PTR(index); p = kzalloc(sizeof(*p), GFP_KERNEL); if (p == NULL) return ERR_PTR(-ENOMEM); p->br = br; dev_hold(dev); p->dev = dev; p->path_cost = port_cost(dev); p->priority = 0x8000 >> BR_PORT_BITS; p->port_no = index; p->flags = BR_LEARNING | BR_FLOOD; br_init_port(p); br_set_state(p, BR_STATE_DISABLED); br_stp_port_timer_init(p); br_multicast_add_port(p); return p; }
相关文章推荐
- 基于mini6410的linux驱动学习总结(五 字符设备驱动程序实例分析(虚拟设备驱动))
- 基于MTD的NANDFLASH设备驱动底层实现原理分析 一
- uboot的GPIO驱动分析--基于全志的A10芯片
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(七)
- 嵌入式驱动开发之--- 虚拟磁盘SBULL块设备驱动程序分析
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(一)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(二)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(一)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(一)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析 二
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(六)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(二)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析 .
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(四)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析 三
- 基于RFID无线射频的设备管理系统---矩阵键盘驱动(按键码)
- uboot的GPIO驱动分析--基于全志的A10芯片【转】
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(二)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(六)
- 基于MTD的NANDFLASH设备驱动底层实现原理分析(七)