最近做了rtl8367的驱动移植,在做的过程中使用了dsa框架,在这向大家分享一下dsa的操作流程和如何使用dsa初始化switch。
设备树
要移植switch驱动首先要初始化设备树,rtl8467支持多种通信方式:smi,spi,mdio等,这里我使用的是mdio通信,所以下面的配置方法也是基于mdio的。首先先展示下设备树的配置:
&gmac1 {
phy-mode = "rgmii-txid";
clock_in_out = "output";
snps,reset-gpio = <&gpio3 RK_PA2 GPIO_ACTIVE_LOW>;
snps,reset-active-low;
/* Reset time is 20ms, 100ms for rtl8211f */
snps,reset-delays-us = <0 20000 100000>;
assigned-clocks = <&cru SCLK_GMAC1_RX_TX>, <&cru SCLK_GMAC1>, <&cru CLK_MAC1_OUT>;
assigned-clock-parents = <&cru SCLK_GMAC1_RGMII_SPEED>;
assigned-clock-rates = <0>, <125000000>, <25000000>;
pinctrl-names = "default";
pinctrl-0 = <&gmac1m1_miim
&gmac1m1_tx_bus2
&gmac1m1_rx_bus2
&gmac1m1_rgmii_clk
&gmac1m1_rgmii_bus
ð1m1_pins>;
tx_delay = <0x47>;
//rx_delay = <0x00>;
status = "okay";
fixed-link {
speed = <1000>;
full-duplex;
pause;
};
};
&mdio1 {
switch0: switch0@29 {
compatible = "rtl8367";
#address-cells = <1>;
#size-cells = <0>;
reg = <29 0>;
dsa,member = <1 0>;
reset= <&gpio3 RK_PA2 GPIO_ACTIVE_LOW>;
clocks = <&cru CLK_MAC1_OUT>;
switch_intc: interrupt-controller {
/* GPIO 15 provides the interrupt */
interrupt-parent = <&gpio3>;
interrupts = <RK_PA1 IRQ_TYPE_LEVEL_HIGH>;
interrupt-controller;
#address-cells = <0>;
#interrupt-cells = <1>;
};
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
label = "lan0";
local-mac-address = [00 11 22 33 44 15];
phy-handle = <&phy0>;
};
port@1 {
reg = <1>;
label = "lan1";
local-mac-address = [00 11 22 33 44 25];
phy-handle = <&phy1>;
};
port@2 {
reg = <2>;
label = "lan2";
local-mac-address = [00 11 22 33 44 35];
phy-handle = <&phy2>;
};
port@3 {
reg = <3>;
label = "lan3";
local-mac-address = [00 11 22 33 44 45];
phy-handle = <&phy3>;
};
port@4 {
reg = <4>;
label = "lan4";
phy-handle = <&phy4>;
local-mac-address = [00 11 22 33 44 55];
};
rtl8366rb_cpu_port: port@6 {
reg = <6>;
label = "cpu";
ethernet = <&gmac1>;
interrupt-parent = <&switch_intc>;
local-mac-address = [00 11 22 33 44 65];
phy-mode = "rgmii";
tx-internal-delay-ps = <2000>;
rx-internal-delay-ps = <2100>;
fixed-link {
speed = <1000>;
full-duplex;
pause;
};
};
};
mdio {
compatible = "realtek,smi-mdio";
#address-cells = <1>;
#size-cells = <0>;
phy0: phy@0 {
interrupt-parent = <&switch_intc>;
reg = <0>;
};
phy1: phy@1 {
interrupt-parent = <&switch_intc>;
reg = <1>;
};
phy2: phy@2 {
interrupt-parent = <&switch_intc>;
reg = <2>;
};
phy3: phy@3 {
interrupt-parent = <&switch_intc>;
reg = <3>;
};
phy4: phy@4 {
interrupt-parent = <&switch_intc>;
reg = <4>;
};
};
};
};
设备树配置有如下几点需要注意
①因为是基于mdio接口来操作的,所以switch节点需要放在对应的mdio下
②switch后面的reg 第一个参数是id,第二个是标号,可以根据情况进行更改
③phy-mode应该设置为rgmii-txid
④禁用自协商使用fixed-link模式,具体操作就是添加下面四行代码到设备树中
fixed-link {
speed = <1000>;
full-duplex;
};
⑤dsa,member = <1 0>;这个属性暂时可以不用管它,当连接多个交换机时需要配置这个属性,后面会进行详细说明
⑥switch中的ports节点不可以随意改名,这是dsa查找拓展网口的地方,后面会进行详细分析,并且switch节点中的mdio子节点并不是我们通信时候的mdio,他只是个名称,可以随意更改。
⑦ ports节点的每个子结点中的reg都地址,需要根据芯片手册进行分析修改
⑧最后这个rtl8366rb_cpu_port
是switch上的phy节点,他的lable必须为cpu,否则设备会找不到主控制节点
⑨ethernet中的gmac1为你要绑定的网络设备,这个需要根据情况进行修改。具体匹配方式后面会进行说明。
⑩tx-internal-delay-ps
与rx-internal-delay-ps
最好查看芯片手册上的说明进行配置,一般手册上都会明确写出范围
以上就是设备树配置中需要注意的问题,接下来来分析rtl8367驱动的移植过程和dsa框架是如何注册多个网络节点的
驱动以及内核源码部分
首先分析下mdio设备的初始化流程:
static const struct of_device_id realtek_mdio_of_match[] = {
{ .compatible= "rtl8367", .data=&rtl8365mb_variant,},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, realtek_mdio_of_match);
static struct mdio_driver realtek_mdio_driver = {
.mdiodrv.driver = {
.of_match_table = realtek_mdio_of_match,
.name = "realtek-mdio",
},
.probe = realtek_mdio_probe,
.remove = realtek_mdio_remove,
};
mdio_module_driver(realtek_mdio_driver);
可以看到和正常的平台设备驱动没什么太大区别,主要是匹配上.driver换成了.mdiodrv.driver ,然后进入到mdio_module_driver中,其实mdio_module_driver也只是一个宏定义,包裹了init和exit函数,而他们也仅仅调用了一个mdio_driver_register
和mdio_driver_unregister
。而这个mdio_driver_register
函数是这样实现的;
int mdio_driver_register(struct mdio_driver *drv)
{
struct mdio_driver_common *mdiodrv = &drv->mdiodrv;
int retval;
pr_debug("%s: %s\n", __func__, mdiodrv->driver.name);
mdiodrv->driver.bus = &mdio_bus_type;
mdiodrv->driver.probe = mdio_probe;
mdiodrv->driver.remove = mdio_remove;
mdiodrv->driver.shutdown = mdio_shutdown;
retval = driver_register(&mdiodrv->driver);
if (retval) {
pr_err("%s: Error %d in registering driver\n",
mdiodrv->driver.name, retval);
return retval;
}
to_net_dev
return 0;
}
EXPORT_SYMBOL(mdio_driver_register);
可以发现,整个mdio_driver_register其实做的就是mdio设备的注册以及初始化,全都完成以后才会进入到我们设定的probe函数中。可以理解为mdio驱动为我们做了一个壳子,壳子里包裹着的就是mdio设备驱动的实现方法,我们完全可以不用管他是怎么实现以及怎么操作的,只需要在正确的位置调用对应的函数或者操作接口就可以将mdio驱动运行起来。从这里分析就可以看到我们移植的时候其实根本不用考虑驱动是如何运作的,至于要将他的读写接口规范其实就完成了一大半。
接下来分析我们rtl8367的probe函数,函数实现如下:
static int realtek_mdio_probe(struct mdio_device *mdiodev)
{
struct realtek_priv *priv;
struct device *dev = &mdiodev->dev;
const struct realtek_variant *var;
struct regmap_config rc;
struct device_node *np;
int ret;
var = of_device_get_match_data(dev);
if (!var)
return -EINVAL;
priv = devm_kzalloc(&mdiodev->dev,sizeof(*priv),GFP_KERNEL);
if (!priv)
return -ENOMEM;
mutex_init(&priv->map_lock);
rc = realtek_mdio_regmap_config;
rc.lock_arg = priv;
priv->map = devm_regmap_init(dev, NULL, priv, &rc);
if (IS_ERR(priv->map)) {
ret = PTR_ERR(priv->map);
dev_err(dev, "regmap init failed: %d\n", ret);
return ret;
}
rc = realtek_mdio_nolock_regmap_config;
priv->map_nolock = devm_regmap_init(dev, NULL, priv, &rc);
if (IS_ERR(priv->map_nolock)) {
ret = PTR_ERR(priv->map_nolock);
dev_err(dev, "regmap init failed: %d\n", ret);
return ret;
}
priv->mdio_addr = mdiodev->addr;
priv->bus = mdiodev->bus;
priv->dev = &mdiodev->dev;
priv->chip_data = (void *)priv + sizeof(*priv);
priv->clk_delay = var->clk_delay;
priv->cmd_read = var->cmd_read;
priv->cmd_write = var->cmd_write;
priv->ops = var->ops;
priv->write_reg_noack = realtek_mdio_write;
np = dev->of_node;
dev_set_drvdata(dev, priv);
/* TODO: if power is software controlled, set up any regulators here */
priv->leds_disabled = of_property_read_bool(np, "realtek,disable-leds");
priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(priv->reset)) {
printk("failed to get RESET GPIO\n");
return PTR_ERR(priv->reset);
}
if (priv->reset) {
gpiod_set_value(priv->reset, 1);
msleep(REALTEK_HW_STOP_DELAY);
gpiod_set_value(priv->reset, 0);
msleep(REALTEK_HW_START_DELAY);
}
ret = priv->ops->detect(priv);
if (ret) {
dev_err(dev, "unable to detect switch\n");
return ret;
}
priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
if (!priv->ds)
return -ENOMEM;
priv->ds->dev = dev;
priv->ds->num_ports = priv->num_ports;
priv->ds->priv = priv;
priv->ds->ops = var->ds_ops_mdio;
ret = dsa_register_switch(priv->ds);
if (ret) {
dev_err(priv->dev, "unable to register switch ret = %d\n", ret);
return ret;
}
return 0;
}
不难发现,其实probe函数做的就是将接口和初始化配置完成,然后进行dsa设备的注册,也就是调用dsa_register_switch
。接下来就分析dsa_register_switch
这个函数就可以了。这个函数实现可以理解为只调用了dsa_switch_probe
而dsa_switch_probe
函数就是整个dsa框架的核心部分,也是我们想知道的将一个网口复用为多个网口的实现部分。函数实现如下:
static int dsa_switch_probe(struct dsa_switch *ds)
{
struct dsa_switch_tree *dst;
struct dsa_chip_data *pdata;
struct device_node *np;
int err;
if (!ds->dev)
return -ENODEV;
pdata = ds->dev->platform_data;
np = ds->dev->of_node;
if (!ds->num_ports)
return -EINVAL;
if (np) {
err = dsa_switch_parse_of(ds, np);
if (err)
dsa_switch_release_ports(ds);
} else if (pdata) {
err = dsa_switch_parse(ds, pdata);
if (err)
dsa_switch_release_ports(ds);
} else {
err = -ENODEV;
}
if (err)
return err;
dst = ds->dst;
dsa_tree_get(dst);
err = dsa_tree_setup(dst);
if (err) {
dsa_switch_release_ports(ds);
dsa_tree_put(dst);
}
return err;
}
首先函数判断了是否有switch节点,这个可以回头看下我们的probe函数priv->ds->dev = dev;
而dev的node节点就是我们最开始使用compatible匹配得到的节点,所以进入到dsa_switch_parse_of
函数,这个函数调用了三个关键部分实现的函数接口,函数原型如下:
static int dsa_switch_parse_of(struct dsa_switch *ds, struct device_node *dn)
{
int err;
err = dsa_switch_parse_member_of(ds, dn);
if (err)
return err;
err = dsa_switch_touch_ports(ds);
if (err)
return err;
return dsa_switch_parse_ports_of(ds, dn);
}
首先就是dsa_switch_parse_member_of
函数,这个函数就是根据设备树中的dsa,member
成员创建dsa树,而dsa树其实就是有几个dsa设备,相关资料以及函数实现如下:
static int dsa_switch_parse_member_of(struct dsa_switch *ds,
struct device_node *dn)
{
u32 m[2] = { 0, 0 };
int sz;
/* Don't error out if this optional property isn't found */
sz = of_property_read_variable_u32_array(dn, "dsa,member", m, 2, 2);
if (sz < 0 && sz != -EINVAL)
return sz;
ds->index = m[1];
ds->dst = dsa_tree_touch(m[0]);
if (!ds->dst)
return -ENOMEM;
return 0;
}
DSA 中的 D 代表分布式(Distributed),因为该子系统的设计能够配置和管理级联交换机(上游和下游相互连接的多个交换机)。这些特定端口在 DSA 术语和代码中称为 dsa port。相互连接的多个交换机的集合称为“交换机树”(switch tree)
我的理解为switch芯片中的上游是一个主phy,也就是我们设备树中cpu节点那个port,而这个port管理着多个从port,也就是外扩的网口。这样就形成了一个树形结构,而他们被叫做switch tree。当我们将dsa,member设置为2时,这个函数就会申请两个tree的空间(调用的dsa_tree_touch
),dsa_tree_touch
函数实现如下
static struct dsa_switch_tree *dsa_tree_touch(int index)
{
struct dsa_switch_tree *dst;
dst = dsa_tree_find(index);
if (dst)
return dsa_tree_get(dst);
else
return dsa_tree_alloc(index);
}
static struct dsa_switch_tree *dsa_tree_find(int index)
{
struct dsa_switch_tree *dst;
list_for_each_entry(dst, &dsa_tree_list, list)
if (dst->index == index)
return dst;
return NULL;
}
static struct dsa_switch_tree *dsa_tree_get(struct dsa_switch_tree *dst)
{
if (dst)
kref_get(&dst->refcount);
return dst;
}
static struct dsa_switch_tree *dsa_tree_alloc(int index)
{
struct dsa_switch_tree *dst;
dst = kzalloc(sizeof(*dst), GFP_KERNEL);
if (!dst)
return NULL;
dst->index = index;
INIT_LIST_HEAD(&dst->rtable);
INIT_LIST_HEAD(&dst->ports);
INIT_LIST_HEAD(&dst->list);
list_add_tail(&dst->list, &dsa_tree_list);
kref_init(&dst->refcount);
return dst;
}
这样看就很清晰了当你把member设置为2,他就会首先遍历他自己的链表,如果有就返回那个链表,否则就申请这个空间并将其添加进链表中。
然后分析第二个函数dsa_switch_touch_ports
这个函数的实现其实和上一个很相似,就是查找我们在probe函数中写的num_ports并进行申请内存和初始化链表。函数实现如下:
static int dsa_switch_touch_ports(struct dsa_switch *ds)
{
struct dsa_port *dp;
int port;
for (port = 0; port < ds->num_ports; port++) {
dp = dsa_port_touch(ds, port);
if (!dp)
return -ENOMEM;
}
return 0;
}
static struct dsa_port *dsa_port_touch(struct dsa_switch *ds, int index)
{
struct dsa_switch_tree *dst = ds->dst;
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list)
if (dp->ds == ds && dp->index == index)
return dp;
dp = kzalloc(sizeof(*dp), GFP_KERNEL);
if (!dp)
return NULL;
dp->ds = ds;
dp->index = index;
INIT_LIST_HEAD(&dp->list);
list_add_tail(&dp->list, &dst->ports);
return dp;
}
接下来是最重要的一步dsa_switch_parse_ports_of
,可以说前两步只是为了这步创建环境,而这一步就是在这个环境中进行对设备的初始化和各种操作。函数实现如下:
static int dsa_switch_parse_ports_of(struct dsa_switch *ds,
struct device_node *dn)
{
struct device_node *ports, *port;
struct dsa_port *dp;
int err = 0;
u32 reg;
ports = of_get_child_by_name(dn, "ports");
if (!ports) {
/* The second possibility is "ethernet-ports" */
ports = of_get_child_by_name(dn, "ethernet-ports");
if (!ports) {
dev_err(ds->dev, "no ports child node found\n");
return -EINVAL;
}
}
for_each_available_child_of_node(ports, port) {
err = of_property_read_u32(port, "reg", ®);
if (err)
goto out_put_node;
if (reg >= ds->num_ports) {
err = -EINVAL;
goto out_put_node;
}
dp = dsa_to_port(ds, reg);
err = dsa_port_parse_of(dp, port);
if (err)
goto out_put_node;
}
out_put_node:
of_node_put(ports);
return err;
}
这里第一步就是找ports节点,如果没有就继续找ethernet-ports
节点,这也是我说为什么不可以随意更改ports节点的名字。然后就是遍历每一个节点的reg,当然,如果reg大于等于我们之前的num_ports 就会报错,其中的原因就是上一步的dsa_switch_touch_ports
如果reg超过了dsa_switch_touch_ports
中申请的范围极有可能造成内存泄漏或者更大的问题,所以这里会返回错误。如果reg为正常值,就会进入到dsa_to_port 为这个port做一个标记,然后进入到dsa_port_parse_of
函数,这个函数就是整个实现中最关键的一步,函数实现如下:
static int dsa_port_parse_of(struct dsa_port *dp, struct device_node *dn)
{
struct device_node *ethernet = of_parse_phandle(dn, "ethernet", 0);
const char *name = of_get_property(dn, "label", NULL);
bool link = of_property_read_bool(dn, "link");
dp->dn = dn;
if (ethernet) {
struct net_device *master;
master = of_find_net_device_by_node(ethernet);
of_node_put(ethernet);
if (!master)
return -EPROBE_DEFER;
return dsa_port_parse_cpu(dp, master);
}
if (link)
return dsa_port_parse_dsa(dp);
return dsa_port_parse_user(dp, name);
}
可以看大,这个函数实现是分两种情况进行的。首先说正常情况:
正常情况下port并没有ethernet节点,所以就不会进入if(ethernet)函数,那么就会进入到dsa_port_parse_user
函数中,函数实现的很简单,如果我们没有给port命名,就会自动分配一个eth%d ,然后将类型赋值为DSA_PORT_TYPE_USER
也就是做个标记。
但如果是cpu节点,也就是主控制节点的话就复杂的多了,首先是of_find_net_device_by_node
在节点中查找网络设备,如果匹配上的话就转到dsa_port_parse_cpu
dsa_port_parse_cpu
函数实现如下
·
static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master)
{
struct dsa_switch *ds = dp->ds;
struct dsa_switch_tree *dst = ds->dst;
const struct dsa_device_ops *tag_ops;
enum dsa_tag_protocol tag_protocol;
tag_protocol = dsa_get_tag_protocol(dp, master);
tag_ops = dsa_tag_driver_get(tag_protocol);
if (IS_ERR(tag_ops)) {
if (PTR_ERR(tag_ops) == -ENOPROTOOPT)
{
return -EPROBE_DEFER;
}
dev_warn(ds->dev, "No tagger for this switch\n");
dp->master = NULL;
return PTR_ERR(tag_ops);
}
dp->master = master;
dp->type = DSA_PORT_TYPE_CPU;
dp->filter = tag_ops->filter;
dp->rcv = tag_ops->rcv;
dp->tag_ops = tag_ops;
dp->dst = dst;
return 0;
}
可以看出dsa_get_tag_protocol
与上一个其实差异不算特别大,只是多了dsa_get_tag_protocol
和dsa_tag_driver_get
函数,那这两个函数的作用是什么呢,其实就是获取tag_protocol并且与目标的tag_protocol进行比对,那么问题来了,什么是tag_protocol呢,官方给出了这样的解释:
DSA 支持许多特定于供应商的标记协议,其中一种是软件定义的 标记协议,以及无标记模式()。
标记协议的确切格式是特定于供应商的,但一般来说,它们 都包含以下内容:
标识以太网帧来自/应发送到哪个端口
提供将此帧转发到管理接口的原因
个人理解就是供应商提供的一个标记,用来使能自己的芯片的标识。如果这部出现了问题一般是内核根本没有这方面的支持,就需要上网查看高版本是否有类似的提交信息了。
后面的就很容易理解了,与之前的差不多,只不过多了操作集以及多种接口的初始化和主设备的移交。
完成了这些dsa_switch_parse_of
函数部分就完成了,接下来还要回到dsa_switch_probe
这里继续往下看下一个函数dsa_tree_get
与 dsa_tree_setup
,上面讲了dsa_tree就是一个ports的一个树形结构,那么dsa_tree_get就是重新获取这个树并让他进入到dsa_tree_setup进行设置。所以我们直接看dsa_tree_setup
就可以了。函数原型如下:
static int dsa_tree_setup(struct dsa_switch_tree *dst)
{
bool complete;
int err;
if (dst->setup) {
pr_err("DSA: tree %d already setup! Disjoint trees?\n",
dst->index);
return -EEXIST;
}
complete = dsa_tree_setup_routing_table(dst);
if (!complete)
return 0;
err = dsa_tree_setup_default_cpu(dst);
if (err)
return err;
err = dsa_tree_setup_switches(dst);
if (err)
goto teardown_default_cpu;
err = dsa_tree_setup_master(dst);
if (err)
goto teardown_switches;
dst->setup = true;
pr_info("DSA: tree %d setup\n", dst->index);
return 0;
teardown_switches:
dsa_tree_teardown_switches(dst);
teardown_default_cpu:
dsa_tree_teardown_default_cpu(dst);
return err;
}
先看dsa_tree_setup_routing_table
吧,它其实就是遍历这个tree结构然后调用了一个dsa_port_setup_routing_table
函数,这两个的函数实现如下:
static bool dsa_tree_setup_routing_table(struct dsa_switch_tree *dst)
{
bool complete = true;
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list) {
if (dsa_port_is_dsa(dp)) {
complete = dsa_port_setup_routing_table(dp);
if (!complete)
break;
}
}
return complete;
}
static bool dsa_port_setup_routing_table(struct dsa_port *dp)
{
struct dsa_switch *ds = dp->ds;
struct dsa_switch_tree *dst = ds->dst;
struct device_node *dn = dp->dn;
struct of_phandle_iterator it;
struct dsa_port *link_dp;
struct dsa_link *dl;
int err;
of_for_each_phandle(&it, err, dn, "link", NULL, 0) {
link_dp = dsa_tree_find_port_by_node(dst, it.node);
if (!link_dp) {
of_node_put(it.node);
return false;
}
dl = dsa_link_touch(dp, link_dp);
if (!dl) {
of_node_put(it.node);
return false;
}
}
return true;
}
可以看出,dsa_port_setup_routing_table
就是查看是否有link节点,如果有的话就返回,没有的话创建。
然后是第二个函数dsa_tree_setup_default_cpu
函数原型如下
static int dsa_tree_setup_default_cpu(struct dsa_switch_tree *dst)
{
struct dsa_port *cpu_dp, *dp;
cpu_dp = dsa_tree_find_first_cpu(dst);
if (!cpu_dp) {
pr_err("DSA: tree %d has no CPU port\n", dst->index);
return -EINVAL;
}
/* Assign the default CPU port to all ports of the fabric */
list_for_each_entry(dp, &dst->ports, list)
if (dsa_port_is_user(dp) || dsa_port_is_dsa(dp))
dp->cpu_dp = cpu_dp;
return 0;
}
static struct dsa_port *dsa_tree_find_first_cpu(struct dsa_switch_tree *dst)
{
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list)
if (dsa_port_is_cpu(dp))
return dp;
return NULL;
}
static bool dsa_port_is_cpu(struct dsa_port *port)
{
return port->type == DSA_PORT_TYPE_CPU;
}
查看代码发现该函数调用了dsa_tree_find_first_cpu
其实就是遍历列表查找type==DSA_PORT_TYPE_CPU ,也就是咱们刚才dsa_port_parse_cpu
中赋值的那个type,如果找到了就跳出,然后将默认端口分配给所有端口,他的目的是为了实现数据包的交换和转发。通过将默认CPU端口分配给fabric的所有端口,可以实现数据包在不同端口之间的传输,并且确保数据包能够按照预定的路由和规则进行转发。这样可以实现网络设备之间的通信和数据传输,从而实现网络的正常运行和数据的传递。
然后是dsa_tree_setup_switches
dsa_tree_setup_switches
函数实现了多个port的状态以及物理层的链接,这里主设备与从设备的驱动方式也有一些不同。相关代码如下:
static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
{
struct dsa_port *dp;
int err;
list_for_each_entry(dp, &dst->ports, list) {
err = dsa_switch_setup(dp->ds);
if (err)
goto teardown;
}
list_for_each_entry(dp, &dst->ports, list) {
err = dsa_port_setup(dp);
if (err) {
dsa_port_devlink_teardown(dp);
dp->type = DSA_PORT_TYPE_UNUSED;
err = dsa_port_devlink_setup(dp);
if (err)
goto teardown;
continue;
}
}
return 0;
teardown:
list_for_each_entry(dp, &dst->ports, list)
dsa_port_teardown(dp);
list_for_each_entry(dp, &dst->ports, list)
dsa_switch_teardown(dp->ds);
return err;
}
static int dsa_port_setup(struct dsa_port *dp)
{
struct devlink_port *dlp = &dp->devlink_port;
bool dsa_port_link_registered = false;
bool dsa_port_enabled = false;
int err = 0;
if (dp->setup)
return 0;
switch (dp->type) {
case DSA_PORT_TYPE_UNUSED:
dsa_port_disable(dp);
break;
case DSA_PORT_TYPE_CPU:
err = dsa_port_link_register_of(dp);
if (err)
break;
dsa_port_link_registered = true;
err = dsa_port_enable(dp, NULL);
if (err)
break;
dsa_port_enabled = true;
break;
case DSA_PORT_TYPE_DSA:
err = dsa_port_link_register_of(dp);
if (err)
break;
dsa_port_link_registered = true;
err = dsa_port_enable(dp, NULL);
if (err)
break;
dsa_port_enabled = true;
break;
case DSA_PORT_TYPE_USER:
dp->mac = of_get_mac_address(dp->dn);
err = dsa_slave_create(dp);
if (err)
break;
devlink_port_type_eth_set(dlp, dp->slave);
break;
}
if (err && dsa_port_enabled)
dsa_port_disable(dp);
if (err && dsa_port_link_registered)
dsa_port_link_unregister_of(dp);
if (err)
return err;
dp->setup = true;
return 0;
}
最后就是dsa_tree_setup_master
这是switch主机的配置,都是一些基础配置了,我把代码附上大家有兴趣可以深入分析下。
static int dsa_tree_setup_master(struct dsa_switch_tree *dst)
{
struct dsa_port *dp;
int err;
list_for_each_entry(dp, &dst->ports, list) {
if (dsa_port_is_cpu(dp)) {
err = dsa_master_setup(dp->master, dp);
if (err)
return err;
}
}
return 0;
}
int dsa_master_setup(struct net_device *dev, struct dsa_port *cpu_dp)
{
struct dsa_switch *ds = cpu_dp->ds;
struct device_link *consumer_link;
int ret;
/* The DSA master must use SET_NETDEV_DEV for this to work. */
consumer_link = device_link_add(ds->dev, dev->dev.parent,
DL_FLAG_AUTOREMOVE_CONSUMER);
if (!consumer_link)
netdev_err(dev,
"Failed to create a device link to DSA switch %s\n",
dev_name(ds->dev));
rtnl_lock();
ret = dev_set_mtu(dev, ETH_DATA_LEN + cpu_dp->tag_ops->overhead);
rtnl_unlock();
if (ret)
netdev_warn(dev, "error %d setting MTU to include DSA overhead\n",
ret);
/* If we use a tagging format that doesn't have an ethertype
* field, make sure that all packets from this point on get
* sent to the tag format's receive function.
*/
wmb();
dev->dsa_ptr = cpu_dp;
lockdep_set_class(&dev->addr_list_lock,
&dsa_master_addr_list_lock_key);
dsa_master_set_promiscuity(dev, 1);
ret = dsa_master_ethtool_setup(dev);
if (ret)
goto out_err_reset_promisc;
dsa_netdev_ops_set(dev, &dsa_netdev_ops);
ret = sysfs_create_group(&dev->dev.kobj, &dsa_group);
if (ret)
goto out_err_ndo_teardown;
return ret;
out_err_ndo_teardown:
dsa_netdev_ops_set(dev, NULL);
dsa_master_ethtool_teardown(dev);
out_err_reset_promisc:
dsa_master_set_promiscuity(dev, -1);
return ret;
}
至此,dsa probe的全部流程就结束了。整个流程其实就围绕着ports这个节点以及节点中的各种属性进行配置,然后创建接口与节点让应用层来调用。
因为是第一次接触dsa,大家如果有一些地方觉得说的不清晰或者有问题希望在评论区留言