diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c index 662ee6a9546..81dfa43d750 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c @@ -416,7 +416,8 @@ static void rtldsa_l2_uc_put_row(struct rtl838x_switch_priv *priv, * Called from the L3 layer * The index in the L2 hash table is filled into nh->l2_id; */ -int rtldsa_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh) +int rtldsa_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh, + bool require_existing) { struct rtl838x_l2_entry e = {}; u64 seed = priv->r->l2_hash_seed(nh->mac, nh->rvid); @@ -436,6 +437,21 @@ int rtldsa_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexth return -1; } + /* With a next hop that has no port, an RTL930x delivers every forwarded + * frame twice, once from the switch and once from the Linux stack. A + * row invented here would be static, and nothing would replace it: + * dsa_user_fdb_event() drops an address the software bridge learns on + * an offloaded port before the driver sees it. A caller that can trap + * the route leaves the slot to the address, and lets the switch learn + * it. + */ + if (require_existing && !e.valid) { + if (nh->l2_installed) + rtldsa_l2_uc_put_row(priv, nh); + nh->l2_installed = false; + return -ENOENT; + } + /* Found an existing (e->valid is true) or empty entry, make it a nexthop entry */ if (nh->l2_installed && nh->l2_id != idx) rtldsa_l2_uc_put_row(priv, nh); diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/l3.c b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/l3.c index ed91c1ada5b..1aa79421999 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/l3.c +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/l3.c @@ -990,6 +990,7 @@ static void otto_l3_route_compact(struct otto_l3_ctrl *ctrl, struct otto_l3_rout static int otto_l3_nexthop_update(struct otto_l3_ctrl *ctrl, __be32 ip_addr, u64 mac) { struct rtl838x_switch_priv *priv = ctrl->priv; + bool require_existing = ctrl->cfg->use_l3_tables; struct otto_l3_route *r; bool known; @@ -1009,6 +1010,8 @@ static int otto_l3_nexthop_update(struct otto_l3_ctrl *ctrl, __be32 ip_addr, u64 return -ENOENT; list_for_each_entry(r, &ctrl->routes_list, list) { + bool no_port; + if (r->gw_ip != ip_addr) continue; @@ -1027,11 +1030,23 @@ static int otto_l3_nexthop_update(struct otto_l3_ctrl *ctrl, __be32 ip_addr, u64 ctrl->cfg->set_egress_mac(ctrl, r->id, mac); /* Update ROUTING table: map gateway-mac and switch-mac id to route id */ - if (!rtldsa_l2_nexthop_add(priv, &r->nh)) + if (!rtldsa_l2_nexthop_add(priv, &r->nh, require_existing)) r->nh.l2_installed = true; + /* A next hop with no port delivers the frame twice, so where the + * route entry can trap, let the CPU route it alone until an update + * brings one, the way otto_l3_fib_add_v4() treats a host route + * with no gateway. A family that routes through a PIE rule keeps + * the rule it had. + */ + no_port = ctrl->cfg->use_l3_tables && + r->nh.port == priv->r->port_ignore; + if (no_port && r->attr.action != ROUTE_ACT_TRAP2CPU) + dev_info(ctrl->dev, "no port for %pI4, routing %pI4/%d in software\n", + &ip_addr, &r->dst_ip, r->prefix_len); + r->attr.valid = true; - r->attr.action = ROUTE_ACT_FORWARD; + r->attr.action = no_port ? ROUTE_ACT_TRAP2CPU : ROUTE_ACT_FORWARD; r->attr.type = ROUTE_TYPE_IP4UC; r->attr.hit = false; /* Reset route-used indicator */ diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h index 058a73fbf04..a012e367e5a 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h @@ -1409,7 +1409,8 @@ static inline struct rtldsa_l2_uc *rtldsa_l2_uc_lookup(struct rtl838x_switch_pri return &priv->l2_uc_map[idx]; } -int rtldsa_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh); +int rtldsa_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh, + bool require_existing); int rtldsa_l2_nexthop_del(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh); #endif /* _RTL838X_H */