# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_RTL83XX) += rtl_otto_dsa.o
-rtl_otto_dsa-objs := common.o dsa.o rtl838x.o rtl839x.o rtl930x.o rtl931x.o debugfs.o qos.o tc.o
+rtl_otto_dsa-objs := common.o dsa.o rtl838x.o rtl839x.o rtl930x.o rtl931x.o debugfs.o qos.o tc.o l3.o
#include <linux/of_net.h>
#include <asm/mach-rtl-otto/mach-rtl-otto.h>
+#include "l3.h"
#include "rtl-otto.h"
int rtldsa_port_get_stp_state(struct rtl838x_switch_priv *priv, int port)
* Called from the L3 layer
* The index in the L2 hash table is filled into nh->l2_id;
*/
-static int rtl83xx_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct rtl83xx_nexthop *nh)
+int rtl83xx_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh)
{
struct rtl838x_l2_entry e;
u64 seed = priv->r->l2_hash_seed(nh->mac, nh->rvid);
* If it was static, the entire entry is removed, otherwise the nexthop bit is cleared
* and we wait until the entry ages out
*/
-static int rtl83xx_l2_nexthop_rm(struct rtl838x_switch_priv *priv, struct rtl83xx_nexthop *nh)
+int rtl83xx_l2_nexthop_rm(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh)
{
struct rtl838x_l2_entry e;
u32 key = nh->l2_id >> 2;
return -EINVAL;
}
-static const struct rhashtable_params route_ht_params = {
- .key_len = sizeof(u32),
- .key_offset = offsetof(struct rtl83xx_route, gw_ip),
- .head_offset = offsetof(struct rtl83xx_route, linkage),
-};
-
-/* Updates an L3 next hop entry in the ROUTING table */
-static int rtl83xx_l3_nexthop_update(struct rtl838x_switch_priv *priv, __be32 ip_addr, u64 mac)
-{
- struct rtl83xx_route *r;
- struct rhlist_head *tmp, *list;
-
- rcu_read_lock();
- list = rhltable_lookup(&priv->routes, &ip_addr, route_ht_params);
- if (!list) {
- rcu_read_unlock();
- return -ENOENT;
- }
-
- rhl_for_each_entry_rcu(r, tmp, list, linkage) {
- pr_debug("%s: Setting up fwding: ip %pI4, GW mac %016llx\n",
- __func__, &ip_addr, mac);
-
- /* Reads the ROUTING table entry associated with the route */
- priv->r->route_read(r->id, r);
- pr_debug("Route with id %d to %pI4 / %d\n", r->id, &r->dst_ip, r->prefix_len);
-
- r->nh.mac = r->nh.gw = mac;
- r->nh.port = priv->r->port_ignore;
- r->nh.id = r->id;
-
- /* Do we need to explicitly add a DMAC entry with the route's nh index? */
- if (priv->r->set_l3_egress_mac)
- priv->r->set_l3_egress_mac(r->id, mac);
-
- /* Update ROUTING table: map gateway-mac and switch-mac id to route id */
- rtl83xx_l2_nexthop_add(priv, &r->nh);
-
- r->attr.valid = true;
- r->attr.action = ROUTE_ACT_FORWARD;
- r->attr.type = 0;
- r->attr.hit = false; /* Reset route-used indicator */
-
- /* Add PIE entry with dst_ip and prefix_len */
- r->pr.dip = r->dst_ip;
- r->pr.dip_m = inet_make_mask(r->prefix_len);
-
- if (r->is_host_route) {
- int slot = priv->r->find_l3_slot(r, false);
-
- pr_info("%s: Got slot for route: %d\n", __func__, slot);
- priv->r->host_route_write(slot, r);
- } else {
- priv->r->route_write(r->id, r);
- r->pr.fwd_sel = true;
- r->pr.fwd_data = r->nh.l2_id;
- r->pr.fwd_act = PIE_ACT_ROUTE_UC;
- }
-
- if (priv->r->set_l3_nexthop)
- priv->r->set_l3_nexthop(r->nh.id, r->nh.l2_id, r->nh.if_id);
-
- if (r->pr.id < 0) {
- r->pr.packet_cntr = rtl83xx_packet_cntr_alloc(priv);
- if (r->pr.packet_cntr >= 0) {
- pr_info("Using packet counter %d\n", r->pr.packet_cntr);
- r->pr.log_sel = true;
- r->pr.log_data = r->pr.packet_cntr;
- }
- priv->r->pie_rule_add(priv, &r->pr);
- } else {
- int pkts = priv->r->packet_cntr_read(r->pr.packet_cntr);
-
- pr_debug("%s: total packets: %d\n", __func__, pkts);
-
- priv->r->pie_rule_write(priv, r->pr.id, &r->pr);
- }
- }
- rcu_read_unlock();
-
- return 0;
-}
-
-static int rtl83xx_port_ipv4_resolve(struct rtl838x_switch_priv *priv,
- struct net_device *dev, __be32 ip_addr)
-{
- struct neighbour *n = neigh_lookup(&arp_tbl, &ip_addr, dev);
- int err = 0;
- u64 mac;
-
- if (!n) {
- n = neigh_create(&arp_tbl, &ip_addr, dev);
- if (IS_ERR(n))
- return PTR_ERR(n);
- }
-
- /* If the neigh is already resolved, then go ahead and
- * install the entry, otherwise start the ARP process to
- * resolve the neigh.
- */
- if (n->nud_state & NUD_VALID) {
- mac = ether_addr_to_u64(n->ha);
- pr_info("%s: resolved mac: %016llx\n", __func__, mac);
- rtl83xx_l3_nexthop_update(priv, ip_addr, mac);
- } else {
- pr_info("%s: need to wait\n", __func__);
- neigh_event_send(n, NULL);
- }
-
- neigh_release(n);
-
- return err;
-}
-
-struct rtl83xx_walk_data {
- struct rtl838x_switch_priv *priv;
- int port;
-};
-
-static int rtl83xx_port_lower_walk(struct net_device *lower, struct netdev_nested_priv *_priv)
-{
- struct rtl83xx_walk_data *data = (struct rtl83xx_walk_data *)_priv->data;
- struct rtl838x_switch_priv *priv = data->priv;
- int ret = 0;
- int index;
-
- index = rtl83xx_port_is_under(lower, priv);
- data->port = index;
- if (index >= 0) {
- pr_debug("Found DSA-port, index %d\n", index);
- ret = 1;
- }
-
- return ret;
-}
-
-static int rtl83xx_port_dev_lower_find(struct net_device *dev, struct rtl838x_switch_priv *priv)
-{
- struct rtl83xx_walk_data data;
- struct netdev_nested_priv _priv;
-
- data.priv = priv;
- data.port = 0;
- _priv.data = (void *)&data;
-
- netdev_walk_all_lower_dev(dev, rtl83xx_port_lower_walk, &_priv);
-
- return data.port;
-}
-
-static struct rtl83xx_route *rtl83xx_route_alloc(struct rtl838x_switch_priv *priv, u32 ip)
-{
- struct rtl83xx_route *r;
- int idx = 0, err;
-
- mutex_lock(&priv->reg_mutex);
-
- idx = find_first_zero_bit(priv->route_use_bm, MAX_ROUTES);
- pr_debug("%s id: %d, ip %pI4\n", __func__, idx, &ip);
-
- r = kzalloc(sizeof(*r), GFP_KERNEL);
- if (!r) {
- mutex_unlock(&priv->reg_mutex);
- return r;
- }
-
- r->id = idx;
- r->gw_ip = ip;
- r->pr.id = -1; /* We still need to allocate a rule in HW */
- r->is_host_route = false;
-
- err = rhltable_insert(&priv->routes, &r->linkage, route_ht_params);
- if (err) {
- pr_err("Could not insert new rule\n");
- mutex_unlock(&priv->reg_mutex);
- goto out_free;
- }
-
- set_bit(idx, priv->route_use_bm);
-
- mutex_unlock(&priv->reg_mutex);
-
- return r;
-
-out_free:
- kfree(r);
-
- return NULL;
-}
-
-static struct rtl83xx_route *rtl83xx_host_route_alloc(struct rtl838x_switch_priv *priv, u32 ip)
-{
- struct rtl83xx_route *r;
- int idx = 0, err;
-
- mutex_lock(&priv->reg_mutex);
-
- idx = find_first_zero_bit(priv->host_route_use_bm, MAX_HOST_ROUTES);
- pr_debug("%s id: %d, ip %pI4\n", __func__, idx, &ip);
-
- r = kzalloc(sizeof(*r), GFP_KERNEL);
- if (!r) {
- mutex_unlock(&priv->reg_mutex);
- return r;
- }
-
- /* We require a unique route ID irrespective of whether it is a prefix or host
- * route (on RTL93xx) as we use this ID to associate a DMAC and next-hop entry
- */
- r->id = idx + MAX_ROUTES;
-
- r->gw_ip = ip;
- r->pr.id = -1; /* We still need to allocate a rule in HW */
- r->is_host_route = true;
-
- err = rhltable_insert(&priv->routes, &r->linkage, route_ht_params);
- if (err) {
- pr_err("Could not insert new rule\n");
- mutex_unlock(&priv->reg_mutex);
- goto out_free;
- }
-
- set_bit(idx, priv->host_route_use_bm);
-
- mutex_unlock(&priv->reg_mutex);
-
- return r;
-
-out_free:
- kfree(r);
-
- return NULL;
-}
-
-static void rtl83xx_route_rm(struct rtl838x_switch_priv *priv, struct rtl83xx_route *r)
-{
- int id;
-
- if (rhltable_remove(&priv->routes, &r->linkage, route_ht_params))
- dev_warn(priv->dev, "Could not remove route\n");
-
- if (r->is_host_route) {
- id = priv->r->find_l3_slot(r, false);
- pr_debug("%s: Got id for host route: %d\n", __func__, id);
- r->attr.valid = false;
- priv->r->host_route_write(id, r);
- clear_bit(r->id - MAX_ROUTES, priv->host_route_use_bm);
- } else {
- /* If there is a HW representation of the route, delete it */
- if (priv->r->route_lookup_hw) {
- id = priv->r->route_lookup_hw(r);
- pr_info("%s: Got id for prefix route: %d\n", __func__, id);
- r->attr.valid = false;
- priv->r->route_write(id, r);
- }
- clear_bit(r->id, priv->route_use_bm);
- }
-
- kfree(r);
-}
-
-static int rtldsa_fib4_check(struct rtl838x_switch_priv *priv,
- struct fib_entry_notifier_info *info,
- enum fib_event_type event)
-{
- struct net_device *ndev = fib_info_nh(info->fi, 0)->fib_nh_dev;
- int vlan = is_vlan_dev(ndev) ? vlan_dev_vlan_id(ndev) : 0;
- struct fib_nh *nh = fib_info_nh(info->fi, 0);
- char gw_message[32] = "";
-
- if (nh->fib_nh_gw4)
- snprintf(gw_message, sizeof(gw_message), "via %pI4 ", &nh->fib_nh_gw4);
-
- dev_info(priv->dev, "%s IPv4 route %pI4/%d %s(VLAN %d, MAC %pM)\n",
- event == FIB_EVENT_ENTRY_ADD ? "add" : "delete",
- &info->dst, info->dst_len, gw_message, vlan, ndev->dev_addr);
-
- if ((info->type == RTN_BROADCAST) || ipv4_is_loopback(info->dst) || !info->dst) {
- dev_warn(priv->dev, "skip loopback/broadcast addresses and default routes\n");
- return -EINVAL;
- }
-
- return 0;
-}
-
-static int rtldsa_fib4_del(struct rtl838x_switch_priv *priv,
- struct fib_entry_notifier_info *info)
-{
- struct fib_nh *nh = fib_info_nh(info->fi, 0);
- struct rhlist_head *tmp, *list;
- struct rtl83xx_route *route;
-
- if (rtldsa_fib4_check(priv, info, FIB_EVENT_ENTRY_DEL))
- return 0;
-
- rcu_read_lock();
- list = rhltable_lookup(&priv->routes, &nh->fib_nh_gw4, route_ht_params);
- if (!list) {
- rcu_read_unlock();
- dev_err(priv->dev, "no such gateway: %pI4\n", &nh->fib_nh_gw4);
- return -ENOENT;
- }
- rhl_for_each_entry_rcu(route, tmp, list, linkage) {
- if (route->dst_ip == info->dst && route->prefix_len == info->dst_len) {
- dev_info(priv->dev, "found a route with id %d, nh-id %d\n",
- route->id, route->nh.id);
- break;
- }
- }
- rcu_read_unlock();
-
- rtl83xx_l2_nexthop_rm(priv, &route->nh);
-
- dev_info(priv->dev, "releasing packet counter %d\n", route->pr.packet_cntr);
- set_bit(route->pr.packet_cntr, priv->packet_cntr_use_bm);
- priv->r->pie_rule_rm(priv, &route->pr);
-
- rtl83xx_route_rm(priv, route);
-
- nh->fib_nh_flags &= ~RTNH_F_OFFLOAD;
-
- return 0;
-}
-
-/* On the RTL93xx, an L3 termination endpoint MAC address on which the router waits
- * for packets to be routed needs to be allocated.
- */
-static int rtl83xx_alloc_router_mac(struct rtl838x_switch_priv *priv, u64 mac)
-{
- int free_mac = -1;
- struct rtl93xx_rt_mac m;
-
- mutex_lock(&priv->reg_mutex);
- for (int i = 0; i < MAX_ROUTER_MACS; i++) {
- priv->r->get_l3_router_mac(i, &m);
- if (free_mac < 0 && !m.valid) {
- free_mac = i;
- continue;
- }
- if (m.valid && m.mac == mac) {
- free_mac = i;
- break;
- }
- }
-
- if (free_mac < 0) {
- pr_err("No free router MACs, cannot offload\n");
- mutex_unlock(&priv->reg_mutex);
- return -1;
- }
-
- m.valid = true;
- m.mac = mac;
- m.p_type = 0; /* An individual port, not a trunk port */
- m.p_id = 0x3f; /* Listen on any port */
- m.p_id_mask = 0;
- m.vid = 0; /* Listen on any VLAN... */
- m.vid_mask = 0; /* ... so mask needs to be 0 */
- m.mac_mask = 0xffffffffffffULL; /* We want an exact match of the interface MAC */
- m.action = L3_FORWARD; /* Route the packet */
- priv->r->set_l3_router_mac(free_mac, &m);
-
- mutex_unlock(&priv->reg_mutex);
-
- return 0;
-}
-
-static int rtl83xx_alloc_egress_intf(struct rtl838x_switch_priv *priv, u64 mac, int vlan)
-{
- int free_mac = -1;
- struct rtl838x_l3_intf intf;
- u64 m;
-
- mutex_lock(&priv->reg_mutex);
- for (int i = 0; i < MAX_SMACS; i++) {
- m = priv->r->get_l3_egress_mac(L3_EGRESS_DMACS + i);
- if (free_mac < 0 && !m) {
- free_mac = i;
- continue;
- }
- if (m == mac) {
- mutex_unlock(&priv->reg_mutex);
- return i;
- }
- }
-
- if (free_mac < 0) {
- pr_err("No free egress interface, cannot offload\n");
- return -1;
- }
-
- /* Set up default egress interface 1 */
- intf.vid = vlan;
- intf.smac_idx = free_mac;
- intf.ip4_mtu_id = 1;
- intf.ip6_mtu_id = 1;
- intf.ttl_scope = 1; /* TTL */
- intf.hl_scope = 1; /* Hop Limit */
- intf.ip4_icmp_redirect = intf.ip6_icmp_redirect = 2; /* FORWARD */
- intf.ip4_pbr_icmp_redirect = intf.ip6_pbr_icmp_redirect = 2; /* FORWARD; */
- priv->r->set_l3_egress_intf(free_mac, &intf);
-
- priv->r->set_l3_egress_mac(L3_EGRESS_DMACS + free_mac, mac);
-
- mutex_unlock(&priv->reg_mutex);
-
- return free_mac;
-}
-
-static int rtldsa_fib4_add(struct rtl838x_switch_priv *priv,
- struct fib_entry_notifier_info *info)
-{
- struct net_device *ndev = fib_info_nh(info->fi, 0)->fib_nh_dev;
- int vlan = is_vlan_dev(ndev) ? vlan_dev_vlan_id(ndev) : 0;
- struct fib_nh *nh = fib_info_nh(info->fi, 0);
- struct rtl83xx_route *route;
- int port;
-
- if (rtldsa_fib4_check(priv, info, FIB_EVENT_ENTRY_ADD))
- return 0;
-
- port = rtl83xx_port_dev_lower_find(ndev, priv);
- if (port < 0) {
- dev_err(priv->dev, "lower interface %s not found\n", ndev->name);
- return -ENODEV;
- }
-
- /* Allocate route or host-route entry (if hardware supports this) */
- if (info->dst_len == 32 && priv->r->host_route_write)
- route = rtl83xx_host_route_alloc(priv, nh->fib_nh_gw4);
- else
- route = rtl83xx_route_alloc(priv, nh->fib_nh_gw4);
-
- if (route)
- dev_info(priv->dev, "route hashtable extended for gw %pI4\n", &nh->fib_nh_gw4);
- else {
- dev_err(priv->dev, "could not extend route hashtable for gw %pI4\n", &nh->fib_nh_gw4);
- return -ENOSPC;
- }
-
- route->dst_ip = info->dst;
- route->prefix_len = info->dst_len;
- route->nh.rvid = vlan;
-
- if (priv->r->set_l3_router_mac) {
- u64 mac = ether_addr_to_u64(ndev->dev_addr);
-
- pr_debug("Local route and router MAC %pM\n", ndev->dev_addr);
- if (rtl83xx_alloc_router_mac(priv, mac))
- goto out_free_rt;
-
- /* vid = 0: Do not care about VID */
- route->nh.if_id = rtl83xx_alloc_egress_intf(priv, mac, vlan);
- if (route->nh.if_id < 0)
- goto out_free_rmac;
-
- if (!nh->fib_nh_gw4) {
- int slot;
-
- route->nh.mac = mac;
- route->nh.port = priv->r->port_ignore;
- route->attr.valid = true;
- route->attr.action = ROUTE_ACT_TRAP2CPU;
- route->attr.type = 0;
-
- slot = priv->r->find_l3_slot(route, false);
- pr_debug("%s: Got slot for route: %d\n", __func__, slot);
- priv->r->host_route_write(slot, route);
- }
- }
-
- /* We need to resolve the mac address of the GW */
- if (nh->fib_nh_gw4)
- rtl83xx_port_ipv4_resolve(priv, ndev, nh->fib_nh_gw4);
-
- nh->fib_nh_flags |= RTNH_F_OFFLOAD;
-
- return 0;
-
-out_free_rmac:
-out_free_rt:
- return 0;
-}
-
-static int rtl83xx_fib6_add(struct rtl838x_switch_priv *priv,
- struct fib6_entry_notifier_info *info)
-{
- pr_debug("In %s\n", __func__);
-/* nh->fib_nh_flags |= RTNH_F_OFFLOAD; */
-
- return 0;
-}
-
-struct net_event_work {
- struct work_struct work;
- struct rtl838x_switch_priv *priv;
- u64 mac;
- u32 gw_addr;
-};
-
-static void rtl83xx_net_event_work_do(struct work_struct *work)
-{
- struct net_event_work *net_work =
- container_of(work, struct net_event_work, work);
- struct rtl838x_switch_priv *priv = net_work->priv;
-
- rtl83xx_l3_nexthop_update(priv, net_work->gw_addr, net_work->mac);
-
- kfree(net_work);
-}
-
-static int rtl83xx_netevent_event(struct notifier_block *this,
- unsigned long event, void *ptr)
-{
- struct rtl838x_switch_priv *priv;
- struct net_device *dev;
- struct neighbour *n = ptr;
- int err, port;
- struct net_event_work *net_work;
-
- priv = container_of(this, struct rtl838x_switch_priv, ne_nb);
-
- switch (event) {
- case NETEVENT_NEIGH_UPDATE:
- /* ignore events for HW with missing L3 offloading implementation */
- if (!priv->r->l3_setup)
- return NOTIFY_DONE;
-
- if (n->tbl != &arp_tbl)
- return NOTIFY_DONE;
- dev = n->dev;
- port = rtl83xx_port_dev_lower_find(dev, priv);
- if (port < 0 || !(n->nud_state & NUD_VALID)) {
- pr_debug("%s: Neigbour invalid, not updating\n", __func__);
- return NOTIFY_DONE;
- }
-
- net_work = kzalloc(sizeof(*net_work), GFP_ATOMIC);
- if (!net_work)
- return NOTIFY_BAD;
-
- INIT_WORK(&net_work->work, rtl83xx_net_event_work_do);
- net_work->priv = priv;
-
- net_work->mac = ether_addr_to_u64(n->ha);
- net_work->gw_addr = *(__be32 *)n->primary_key;
-
- pr_debug("%s: updating neighbour on port %d, mac %016llx\n",
- __func__, port, net_work->mac);
- queue_work(priv->wq, &net_work->work);
- if (err)
- netdev_warn(dev, "failed to handle neigh update (err %d)\n", err);
- break;
- }
-
- return NOTIFY_DONE;
-}
-
-struct rtl83xx_fib_event_work {
- struct work_struct work;
- union {
- struct fib_entry_notifier_info fen_info;
- struct fib6_entry_notifier_info fen6_info;
- struct fib_rule_notifier_info fr_info;
- };
- struct rtl838x_switch_priv *priv;
- bool is_fib6;
- unsigned long event;
-};
-
-static void rtl83xx_fib_event_work_do(struct work_struct *work)
-{
- struct rtl83xx_fib_event_work *fib_work =
- container_of(work, struct rtl83xx_fib_event_work, work);
- struct rtl838x_switch_priv *priv = fib_work->priv;
- struct fib_rule *rule;
- int err;
-
- /* Protect internal structures from changes */
- rtnl_lock();
- pr_debug("%s: doing work, event %ld\n", __func__, fib_work->event);
- switch (fib_work->event) {
- case FIB_EVENT_ENTRY_ADD:
- case FIB_EVENT_ENTRY_REPLACE:
- case FIB_EVENT_ENTRY_APPEND:
- if (fib_work->is_fib6)
- err = rtl83xx_fib6_add(priv, &fib_work->fen6_info);
- else
- err = rtldsa_fib4_add(priv, &fib_work->fen_info);
- if (err)
- dev_err(priv->dev, "fib_add() failed\n");
-
- fib_info_put(fib_work->fen_info.fi);
- break;
- case FIB_EVENT_ENTRY_DEL:
- err = rtldsa_fib4_del(priv, &fib_work->fen_info);
- if (err)
- dev_err(priv->dev, "fib_del() failed\n");
-
- fib_info_put(fib_work->fen_info.fi);
- break;
- case FIB_EVENT_RULE_ADD:
- case FIB_EVENT_RULE_DEL:
- rule = fib_work->fr_info.rule;
- if (!fib4_rule_default(rule))
- pr_err("%s: FIB4 default rule failed\n", __func__);
- fib_rule_put(rule);
- break;
- }
- rtnl_unlock();
- kfree(fib_work);
-}
-
-/* Called with rcu_read_lock() */
-static int rtl83xx_fib_event(struct notifier_block *this, unsigned long event, void *ptr)
-{
- struct fib_notifier_info *info = ptr;
- struct rtl838x_switch_priv *priv;
- struct rtl83xx_fib_event_work *fib_work;
-
- if ((info->family != AF_INET && info->family != AF_INET6 &&
- info->family != RTNL_FAMILY_IPMR &&
- info->family != RTNL_FAMILY_IP6MR))
- return NOTIFY_DONE;
-
- priv = container_of(this, struct rtl838x_switch_priv, fib_nb);
-
- /* ignore FIB events for HW with missing L3 offloading implementation */
- if (!priv->r->l3_setup)
- return NOTIFY_DONE;
-
- fib_work = kzalloc(sizeof(*fib_work), GFP_ATOMIC);
- if (!fib_work)
- return NOTIFY_BAD;
-
- INIT_WORK(&fib_work->work, rtl83xx_fib_event_work_do);
- fib_work->priv = priv;
- fib_work->event = event;
- fib_work->is_fib6 = false;
-
- switch (event) {
- case FIB_EVENT_ENTRY_ADD:
- case FIB_EVENT_ENTRY_REPLACE:
- case FIB_EVENT_ENTRY_APPEND:
- case FIB_EVENT_ENTRY_DEL:
- pr_debug("%s: FIB_ENTRY ADD/DEL, event %ld\n", __func__, event);
- if (info->family == AF_INET) {
- struct fib_entry_notifier_info *fen_info = ptr;
-
- if (fen_info->fi->fib_nh_is_v6) {
- NL_SET_ERR_MSG_MOD(info->extack,
- "IPv6 gateway with IPv4 route is not supported");
- kfree(fib_work);
- return notifier_from_errno(-EINVAL);
- }
-
- memcpy(&fib_work->fen_info, ptr, sizeof(fib_work->fen_info));
- /* Take referece on fib_info to prevent it from being
- * freed while work is queued. Release it afterwards.
- */
- fib_info_hold(fib_work->fen_info.fi);
-
- } else if (info->family == AF_INET6) {
- //struct fib6_entry_notifier_info *fen6_info = ptr;
- pr_warn("%s: FIB_RULE ADD/DEL for IPv6 not supported\n", __func__);
- kfree(fib_work);
- return NOTIFY_DONE;
- }
- break;
-
- case FIB_EVENT_RULE_ADD:
- case FIB_EVENT_RULE_DEL:
- pr_debug("%s: FIB_RULE ADD/DEL, event: %ld\n", __func__, event);
- memcpy(&fib_work->fr_info, ptr, sizeof(fib_work->fr_info));
- fib_rule_get(fib_work->fr_info.rule);
- break;
- }
-
- queue_work(priv->wq, &fib_work->work);
-
- return NOTIFY_DONE;
-}
-
static irqreturn_t rtldsa_switch_irq(int irq, void *dev_id)
{
struct rtl838x_switch_priv *priv;
priv->mirror_group_ports[i] = -1;
/* Initialize hash table for L3 routing */
- rhltable_init(&priv->routes, &route_ht_params);
+ rhltable_init(&priv->routes, &otto_l3_route_ht_params);
/* Register netevent notifier callback to catch notifications about neighboring
* changes to update nexthop entries for L3 routing.
*/
- priv->ne_nb.notifier_call = rtl83xx_netevent_event;
+ priv->ne_nb.notifier_call = otto_l3_netevent_notifier;
if (register_netevent_notifier(&priv->ne_nb)) {
priv->ne_nb.notifier_call = NULL;
dev_err(dev, "Failed to register netevent notifier\n");
goto err_register_ne_nb;
}
- priv->fib_nb.notifier_call = rtl83xx_fib_event;
+ priv->fib_nb.notifier_call = otto_l3_fib_notifier;
/* Register Forwarding Information Base notifier to offload routes where
* possible
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/if_vlan.h>
+#include <linux/inetdevice.h>
+#include <linux/notifier.h>
+#include <linux/rhashtable.h>
+#include <net/arp.h>
+#include <net/fib_notifier.h>
+#include <net/ip6_fib.h>
+#include <net/netevent.h>
+#include <net/nexthop.h>
+#include <uapi/linux/rtnetlink.h>
+
+#include "l3.h"
+#include "rtl-otto.h"
+
+struct otto_l3_net_event_work {
+ struct work_struct work;
+ struct rtl838x_switch_priv *priv;
+ u64 mac;
+ u32 gw_addr;
+};
+
+struct otto_l3_fib_event_work {
+ struct work_struct work;
+ union {
+ struct fib_entry_notifier_info fen_info;
+ struct fib6_entry_notifier_info fen6_info;
+ struct fib_rule_notifier_info fr_info;
+ };
+ struct rtl838x_switch_priv *priv;
+ bool is_fib6;
+ unsigned long event;
+};
+
+struct otto_l3_walk_data {
+ struct rtl838x_switch_priv *priv;
+ int port;
+};
+
+static int otto_l3_port_lower_walk(struct net_device *lower, struct netdev_nested_priv *_priv)
+{
+ struct otto_l3_walk_data *data = (struct otto_l3_walk_data *)_priv->data;
+ struct rtl838x_switch_priv *priv = data->priv;
+ int ret = 0;
+ int index;
+
+ index = rtl83xx_port_is_under(lower, priv);
+ data->port = index;
+ if (index >= 0) {
+ pr_debug("Found DSA-port, index %d\n", index);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static int otto_l3_port_dev_lower_find(struct net_device *dev, struct rtl838x_switch_priv *priv)
+{
+ struct otto_l3_walk_data data;
+ struct netdev_nested_priv _priv;
+
+ data.priv = priv;
+ data.port = 0;
+ _priv.data = (void *)&data;
+
+ netdev_walk_all_lower_dev(dev, otto_l3_port_lower_walk, &_priv);
+
+ return data.port;
+}
+
+/* On the RTL93xx, an L3 termination endpoint MAC address on which the router waits
+ * for packets to be routed needs to be allocated.
+ */
+static int otto_l3_alloc_router_mac(struct rtl838x_switch_priv *priv, u64 mac)
+{
+ int free_mac = -1;
+ struct rtl93xx_rt_mac m;
+
+ mutex_lock(&priv->reg_mutex);
+ for (int i = 0; i < MAX_ROUTER_MACS; i++) {
+ priv->r->get_l3_router_mac(i, &m);
+ if (free_mac < 0 && !m.valid) {
+ free_mac = i;
+ continue;
+ }
+ if (m.valid && m.mac == mac) {
+ free_mac = i;
+ break;
+ }
+ }
+
+ if (free_mac < 0) {
+ pr_err("No free router MACs, cannot offload\n");
+ mutex_unlock(&priv->reg_mutex);
+ return -1;
+ }
+
+ m.valid = true;
+ m.mac = mac;
+ m.p_type = 0; /* An individual port, not a trunk port */
+ m.p_id = 0x3f; /* Listen on any port */
+ m.p_id_mask = 0;
+ m.vid = 0; /* Listen on any VLAN... */
+ m.vid_mask = 0; /* ... so mask needs to be 0 */
+ m.mac_mask = 0xffffffffffffULL; /* We want an exact match of the interface MAC */
+ m.action = L3_FORWARD; /* Route the packet */
+ priv->r->set_l3_router_mac(free_mac, &m);
+
+ mutex_unlock(&priv->reg_mutex);
+
+ return 0;
+}
+
+static int otto_l3_alloc_egress_intf(struct rtl838x_switch_priv *priv, u64 mac, int vlan)
+{
+ int free_mac = -1;
+ struct rtl838x_l3_intf intf;
+ u64 m;
+
+ mutex_lock(&priv->reg_mutex);
+ for (int i = 0; i < MAX_SMACS; i++) {
+ m = priv->r->get_l3_egress_mac(L3_EGRESS_DMACS + i);
+ if (free_mac < 0 && !m) {
+ free_mac = i;
+ continue;
+ }
+ if (m == mac) {
+ mutex_unlock(&priv->reg_mutex);
+ return i;
+ }
+ }
+
+ if (free_mac < 0) {
+ pr_err("No free egress interface, cannot offload\n");
+ return -1;
+ }
+
+ /* Set up default egress interface 1 */
+ intf.vid = vlan;
+ intf.smac_idx = free_mac;
+ intf.ip4_mtu_id = 1;
+ intf.ip6_mtu_id = 1;
+ intf.ttl_scope = 1; /* TTL */
+ intf.hl_scope = 1; /* Hop Limit */
+ intf.ip4_icmp_redirect = intf.ip6_icmp_redirect = 2; /* FORWARD */
+ intf.ip4_pbr_icmp_redirect = intf.ip6_pbr_icmp_redirect = 2; /* FORWARD; */
+ priv->r->set_l3_egress_intf(free_mac, &intf);
+
+ priv->r->set_l3_egress_mac(L3_EGRESS_DMACS + free_mac, mac);
+
+ mutex_unlock(&priv->reg_mutex);
+
+ return free_mac;
+}
+
+/* Updates an L3 next hop entry in the ROUTING table */
+static int otto_l3_nexthop_update(struct rtl838x_switch_priv *priv, __be32 ip_addr, u64 mac)
+{
+ struct otto_l3_route *r;
+ struct rhlist_head *tmp, *list;
+
+ rcu_read_lock();
+ list = rhltable_lookup(&priv->routes, &ip_addr, otto_l3_route_ht_params);
+ if (!list) {
+ rcu_read_unlock();
+ return -ENOENT;
+ }
+
+ rhl_for_each_entry_rcu(r, tmp, list, linkage) {
+ pr_debug("%s: Setting up fwding: ip %pI4, GW mac %016llx\n",
+ __func__, &ip_addr, mac);
+
+ /* Reads the ROUTING table entry associated with the route */
+ priv->r->route_read(r->id, r);
+ pr_debug("Route with id %d to %pI4 / %d\n", r->id, &r->dst_ip, r->prefix_len);
+
+ r->nh.mac = r->nh.gw = mac;
+ r->nh.port = priv->r->port_ignore;
+ r->nh.id = r->id;
+
+ /* Do we need to explicitly add a DMAC entry with the route's nh index? */
+ if (priv->r->set_l3_egress_mac)
+ priv->r->set_l3_egress_mac(r->id, mac);
+
+ /* Update ROUTING table: map gateway-mac and switch-mac id to route id */
+ rtl83xx_l2_nexthop_add(priv, &r->nh);
+
+ r->attr.valid = true;
+ r->attr.action = ROUTE_ACT_FORWARD;
+ r->attr.type = 0;
+ r->attr.hit = false; /* Reset route-used indicator */
+
+ /* Add PIE entry with dst_ip and prefix_len */
+ r->pr.dip = r->dst_ip;
+ r->pr.dip_m = inet_make_mask(r->prefix_len);
+
+ if (r->is_host_route) {
+ int slot = priv->r->find_l3_slot(r, false);
+
+ pr_info("%s: Got slot for route: %d\n", __func__, slot);
+ priv->r->host_route_write(slot, r);
+ } else {
+ priv->r->route_write(r->id, r);
+ r->pr.fwd_sel = true;
+ r->pr.fwd_data = r->nh.l2_id;
+ r->pr.fwd_act = PIE_ACT_ROUTE_UC;
+ }
+
+ if (priv->r->set_l3_nexthop)
+ priv->r->set_l3_nexthop(r->nh.id, r->nh.l2_id, r->nh.if_id);
+
+ if (r->pr.id < 0) {
+ r->pr.packet_cntr = rtl83xx_packet_cntr_alloc(priv);
+ if (r->pr.packet_cntr >= 0) {
+ pr_info("Using packet counter %d\n", r->pr.packet_cntr);
+ r->pr.log_sel = true;
+ r->pr.log_data = r->pr.packet_cntr;
+ }
+ priv->r->pie_rule_add(priv, &r->pr);
+ } else {
+ int pkts = priv->r->packet_cntr_read(r->pr.packet_cntr);
+
+ pr_debug("%s: total packets: %d\n", __func__, pkts);
+
+ priv->r->pie_rule_write(priv, r->pr.id, &r->pr);
+ }
+ }
+ rcu_read_unlock();
+
+ return 0;
+}
+
+static int otto_l3_port_ipv4_resolve(struct rtl838x_switch_priv *priv,
+ struct net_device *dev, __be32 ip_addr)
+{
+ struct neighbour *n = neigh_lookup(&arp_tbl, &ip_addr, dev);
+ int err = 0;
+ u64 mac;
+
+ if (!n) {
+ n = neigh_create(&arp_tbl, &ip_addr, dev);
+ if (IS_ERR(n))
+ return PTR_ERR(n);
+ }
+
+ /* If the neigh is already resolved, then go ahead and
+ * install the entry, otherwise start the ARP process to
+ * resolve the neigh.
+ */
+ if (n->nud_state & NUD_VALID) {
+ mac = ether_addr_to_u64(n->ha);
+ pr_info("%s: resolved mac: %016llx\n", __func__, mac);
+ otto_l3_nexthop_update(priv, ip_addr, mac);
+ } else {
+ pr_info("%s: need to wait\n", __func__);
+ neigh_event_send(n, NULL);
+ }
+
+ neigh_release(n);
+
+ return err;
+}
+
+static void otto_l3_route_remove(struct rtl838x_switch_priv *priv, struct otto_l3_route *r)
+{
+ int id;
+
+ if (rhltable_remove(&priv->routes, &r->linkage, otto_l3_route_ht_params))
+ dev_warn(priv->dev, "Could not remove route\n");
+
+ if (r->is_host_route) {
+ id = priv->r->find_l3_slot(r, false);
+ pr_debug("%s: Got id for host route: %d\n", __func__, id);
+ r->attr.valid = false;
+ priv->r->host_route_write(id, r);
+ clear_bit(r->id - MAX_ROUTES, priv->host_route_use_bm);
+ } else {
+ /* If there is a HW representation of the route, delete it */
+ if (priv->r->route_lookup_hw) {
+ id = priv->r->route_lookup_hw(r);
+ pr_info("%s: Got id for prefix route: %d\n", __func__, id);
+ r->attr.valid = false;
+ priv->r->route_write(id, r);
+ }
+ clear_bit(r->id, priv->route_use_bm);
+ }
+
+ kfree(r);
+}
+
+static struct otto_l3_route *otto_l3_host_route_alloc(struct rtl838x_switch_priv *priv, u32 ip)
+{
+ struct otto_l3_route *r;
+ int idx = 0, err;
+
+ mutex_lock(&priv->reg_mutex);
+
+ idx = find_first_zero_bit(priv->host_route_use_bm, MAX_HOST_ROUTES);
+ pr_debug("%s id: %d, ip %pI4\n", __func__, idx, &ip);
+
+ r = kzalloc(sizeof(*r), GFP_KERNEL);
+ if (!r) {
+ mutex_unlock(&priv->reg_mutex);
+ return r;
+ }
+
+ /* We require a unique route ID irrespective of whether it is a prefix or host
+ * route (on RTL93xx) as we use this ID to associate a DMAC and next-hop entry
+ */
+ r->id = idx + MAX_ROUTES;
+
+ r->gw_ip = ip;
+ r->pr.id = -1; /* We still need to allocate a rule in HW */
+ r->is_host_route = true;
+
+ err = rhltable_insert(&priv->routes, &r->linkage, otto_l3_route_ht_params);
+ if (err) {
+ pr_err("Could not insert new rule\n");
+ mutex_unlock(&priv->reg_mutex);
+ goto out_free;
+ }
+
+ set_bit(idx, priv->host_route_use_bm);
+
+ mutex_unlock(&priv->reg_mutex);
+
+ return r;
+
+out_free:
+ kfree(r);
+
+ return NULL;
+}
+
+static struct otto_l3_route *otto_l3_route_alloc(struct rtl838x_switch_priv *priv, u32 ip)
+{
+ struct otto_l3_route *r;
+ int idx = 0, err;
+
+ mutex_lock(&priv->reg_mutex);
+
+ idx = find_first_zero_bit(priv->route_use_bm, MAX_ROUTES);
+ pr_debug("%s id: %d, ip %pI4\n", __func__, idx, &ip);
+
+ r = kzalloc(sizeof(*r), GFP_KERNEL);
+ if (!r) {
+ mutex_unlock(&priv->reg_mutex);
+ return r;
+ }
+
+ r->id = idx;
+ r->gw_ip = ip;
+ r->pr.id = -1; /* We still need to allocate a rule in HW */
+ r->is_host_route = false;
+
+ err = rhltable_insert(&priv->routes, &r->linkage, otto_l3_route_ht_params);
+ if (err) {
+ pr_err("Could not insert new rule\n");
+ mutex_unlock(&priv->reg_mutex);
+ goto out_free;
+ }
+
+ set_bit(idx, priv->route_use_bm);
+
+ mutex_unlock(&priv->reg_mutex);
+
+ return r;
+
+out_free:
+ kfree(r);
+
+ return NULL;
+}
+
+static int otto_l3_fib_check_v4(struct rtl838x_switch_priv *priv,
+ struct fib_entry_notifier_info *info,
+ enum fib_event_type event)
+{
+ struct net_device *ndev = fib_info_nh(info->fi, 0)->fib_nh_dev;
+ int vlan = is_vlan_dev(ndev) ? vlan_dev_vlan_id(ndev) : 0;
+ struct fib_nh *nh = fib_info_nh(info->fi, 0);
+ char gw_message[32] = "";
+
+ if (nh->fib_nh_gw4)
+ snprintf(gw_message, sizeof(gw_message), "via %pI4 ", &nh->fib_nh_gw4);
+
+ dev_info(priv->dev, "%s IPv4 route %pI4/%d %s(VLAN %d, MAC %pM)\n",
+ event == FIB_EVENT_ENTRY_ADD ? "add" : "delete",
+ &info->dst, info->dst_len, gw_message, vlan, ndev->dev_addr);
+
+ if ((info->type == RTN_BROADCAST) || ipv4_is_loopback(info->dst) || !info->dst) {
+ dev_warn(priv->dev, "skip loopback/broadcast addresses and default routes\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int otto_l3_fib_add_v4(struct rtl838x_switch_priv *priv,
+ struct fib_entry_notifier_info *info)
+{
+ struct net_device *ndev = fib_info_nh(info->fi, 0)->fib_nh_dev;
+ int vlan = is_vlan_dev(ndev) ? vlan_dev_vlan_id(ndev) : 0;
+ struct fib_nh *nh = fib_info_nh(info->fi, 0);
+ struct otto_l3_route *route;
+ int port;
+
+ if (otto_l3_fib_check_v4(priv, info, FIB_EVENT_ENTRY_ADD))
+ return 0;
+
+ port = otto_l3_port_dev_lower_find(ndev, priv);
+ if (port < 0) {
+ dev_err(priv->dev, "lower interface %s not found\n", ndev->name);
+ return -ENODEV;
+ }
+
+ /* Allocate route or host-route entry (if hardware supports this) */
+ if (info->dst_len == 32 && priv->r->host_route_write)
+ route = otto_l3_host_route_alloc(priv, nh->fib_nh_gw4);
+ else
+ route = otto_l3_route_alloc(priv, nh->fib_nh_gw4);
+
+ if (route)
+ dev_info(priv->dev, "route hashtable extended for gw %pI4\n", &nh->fib_nh_gw4);
+ else {
+ dev_err(priv->dev, "could not extend route hashtable for gw %pI4\n", &nh->fib_nh_gw4);
+ return -ENOSPC;
+ }
+
+ route->dst_ip = info->dst;
+ route->prefix_len = info->dst_len;
+ route->nh.rvid = vlan;
+
+ if (priv->r->set_l3_router_mac) {
+ u64 mac = ether_addr_to_u64(ndev->dev_addr);
+
+ pr_debug("Local route and router MAC %pM\n", ndev->dev_addr);
+ if (otto_l3_alloc_router_mac(priv, mac))
+ goto out_free_rt;
+
+ /* vid = 0: Do not care about VID */
+ route->nh.if_id = otto_l3_alloc_egress_intf(priv, mac, vlan);
+ if (route->nh.if_id < 0)
+ goto out_free_rmac;
+
+ if (!nh->fib_nh_gw4) {
+ int slot;
+
+ route->nh.mac = mac;
+ route->nh.port = priv->r->port_ignore;
+ route->attr.valid = true;
+ route->attr.action = ROUTE_ACT_TRAP2CPU;
+ route->attr.type = 0;
+
+ slot = priv->r->find_l3_slot(route, false);
+ pr_debug("%s: Got slot for route: %d\n", __func__, slot);
+ priv->r->host_route_write(slot, route);
+ }
+ }
+
+ /* We need to resolve the mac address of the GW */
+ if (nh->fib_nh_gw4)
+ otto_l3_port_ipv4_resolve(priv, ndev, nh->fib_nh_gw4);
+
+ nh->fib_nh_flags |= RTNH_F_OFFLOAD;
+
+ return 0;
+
+out_free_rmac:
+out_free_rt:
+ return 0;
+}
+
+static int otto_l3_fib_del_v4(struct rtl838x_switch_priv *priv,
+ struct fib_entry_notifier_info *info)
+{
+ struct fib_nh *nh = fib_info_nh(info->fi, 0);
+ struct rhlist_head *tmp, *list;
+ struct otto_l3_route *route;
+
+ if (otto_l3_fib_check_v4(priv, info, FIB_EVENT_ENTRY_DEL))
+ return 0;
+
+ rcu_read_lock();
+ list = rhltable_lookup(&priv->routes, &nh->fib_nh_gw4, otto_l3_route_ht_params);
+ if (!list) {
+ rcu_read_unlock();
+ dev_err(priv->dev, "no such gateway: %pI4\n", &nh->fib_nh_gw4);
+ return -ENOENT;
+ }
+ rhl_for_each_entry_rcu(route, tmp, list, linkage) {
+ if (route->dst_ip == info->dst && route->prefix_len == info->dst_len) {
+ dev_info(priv->dev, "found a route with id %d, nh-id %d\n",
+ route->id, route->nh.id);
+ break;
+ }
+ }
+ rcu_read_unlock();
+
+ rtl83xx_l2_nexthop_rm(priv, &route->nh);
+
+ dev_info(priv->dev, "releasing packet counter %d\n", route->pr.packet_cntr);
+ set_bit(route->pr.packet_cntr, priv->packet_cntr_use_bm);
+ priv->r->pie_rule_rm(priv, &route->pr);
+
+ otto_l3_route_remove(priv, route);
+
+ nh->fib_nh_flags &= ~RTNH_F_OFFLOAD;
+
+ return 0;
+}
+
+static int otto_l3_fib_add_v6(struct rtl838x_switch_priv *priv,
+ struct fib6_entry_notifier_info *info)
+{
+ pr_debug("In %s\n", __func__);
+/* nh->fib_nh_flags |= RTNH_F_OFFLOAD; */
+
+ return 0;
+}
+
+static void otto_l3_fib_event_work_do(struct work_struct *work)
+{
+ struct otto_l3_fib_event_work *fib_work =
+ container_of(work, struct otto_l3_fib_event_work, work);
+ struct rtl838x_switch_priv *priv = fib_work->priv;
+ struct fib_rule *rule;
+ int err;
+
+ /* Protect internal structures from changes */
+ rtnl_lock();
+ pr_debug("%s: doing work, event %ld\n", __func__, fib_work->event);
+ switch (fib_work->event) {
+ case FIB_EVENT_ENTRY_ADD:
+ case FIB_EVENT_ENTRY_REPLACE:
+ case FIB_EVENT_ENTRY_APPEND:
+ if (fib_work->is_fib6)
+ err = otto_l3_fib_add_v6(priv, &fib_work->fen6_info);
+ else
+ err = otto_l3_fib_add_v4(priv, &fib_work->fen_info);
+ if (err)
+ dev_err(priv->dev, "fib_add() failed\n");
+
+ fib_info_put(fib_work->fen_info.fi);
+ break;
+ case FIB_EVENT_ENTRY_DEL:
+ err = otto_l3_fib_del_v4(priv, &fib_work->fen_info);
+ if (err)
+ dev_err(priv->dev, "fib_del() failed\n");
+
+ fib_info_put(fib_work->fen_info.fi);
+ break;
+ case FIB_EVENT_RULE_ADD:
+ case FIB_EVENT_RULE_DEL:
+ rule = fib_work->fr_info.rule;
+ if (!fib4_rule_default(rule))
+ pr_err("%s: FIB4 default rule failed\n", __func__);
+ fib_rule_put(rule);
+ break;
+ }
+ rtnl_unlock();
+ kfree(fib_work);
+}
+
+
+/* Called with rcu_read_lock() */
+int otto_l3_fib_notifier(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct fib_notifier_info *info = ptr;
+ struct rtl838x_switch_priv *priv;
+ struct otto_l3_fib_event_work *fib_work;
+
+ if ((info->family != AF_INET && info->family != AF_INET6 &&
+ info->family != RTNL_FAMILY_IPMR &&
+ info->family != RTNL_FAMILY_IP6MR))
+ return NOTIFY_DONE;
+
+ priv = container_of(this, struct rtl838x_switch_priv, fib_nb);
+
+ /* ignore FIB events for HW with missing L3 offloading implementation */
+ if (!priv->r->l3_setup)
+ return NOTIFY_DONE;
+
+ fib_work = kzalloc(sizeof(*fib_work), GFP_ATOMIC);
+ if (!fib_work)
+ return NOTIFY_BAD;
+
+ INIT_WORK(&fib_work->work, otto_l3_fib_event_work_do);
+ fib_work->priv = priv;
+ fib_work->event = event;
+ fib_work->is_fib6 = false;
+
+ switch (event) {
+ case FIB_EVENT_ENTRY_ADD:
+ case FIB_EVENT_ENTRY_REPLACE:
+ case FIB_EVENT_ENTRY_APPEND:
+ case FIB_EVENT_ENTRY_DEL:
+ pr_debug("%s: FIB_ENTRY ADD/DEL, event %ld\n", __func__, event);
+ if (info->family == AF_INET) {
+ struct fib_entry_notifier_info *fen_info = ptr;
+
+ if (fen_info->fi->fib_nh_is_v6) {
+ NL_SET_ERR_MSG_MOD(info->extack,
+ "IPv6 gateway with IPv4 route is not supported");
+ kfree(fib_work);
+ return notifier_from_errno(-EINVAL);
+ }
+
+ memcpy(&fib_work->fen_info, ptr, sizeof(fib_work->fen_info));
+ /* Take referece on fib_info to prevent it from being
+ * freed while work is queued. Release it afterwards.
+ */
+ fib_info_hold(fib_work->fen_info.fi);
+
+ } else if (info->family == AF_INET6) {
+ //struct fib6_entry_notifier_info *fen6_info = ptr;
+ pr_warn("%s: FIB_RULE ADD/DEL for IPv6 not supported\n", __func__);
+ kfree(fib_work);
+ return NOTIFY_DONE;
+ }
+ break;
+
+ case FIB_EVENT_RULE_ADD:
+ case FIB_EVENT_RULE_DEL:
+ pr_debug("%s: FIB_RULE ADD/DEL, event: %ld\n", __func__, event);
+ memcpy(&fib_work->fr_info, ptr, sizeof(fib_work->fr_info));
+ fib_rule_get(fib_work->fr_info.rule);
+ break;
+ }
+
+ queue_work(priv->wq, &fib_work->work);
+
+ return NOTIFY_DONE;
+}
+
+static void otto_l3_net_event_work_do(struct work_struct *work)
+{
+ struct otto_l3_net_event_work *net_work =
+ container_of(work, struct otto_l3_net_event_work, work);
+ struct rtl838x_switch_priv *priv = net_work->priv;
+
+ otto_l3_nexthop_update(priv, net_work->gw_addr, net_work->mac);
+
+ kfree(net_work);
+}
+
+int otto_l3_netevent_notifier(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct rtl838x_switch_priv *priv;
+ struct net_device *dev;
+ struct neighbour *n = ptr;
+ int err, port;
+ struct otto_l3_net_event_work *net_work;
+
+ priv = container_of(this, struct rtl838x_switch_priv, ne_nb);
+
+ switch (event) {
+ case NETEVENT_NEIGH_UPDATE:
+ /* ignore events for HW with missing L3 offloading implementation */
+ if (!priv->r->l3_setup)
+ return NOTIFY_DONE;
+
+ if (n->tbl != &arp_tbl)
+ return NOTIFY_DONE;
+ dev = n->dev;
+ port = otto_l3_port_dev_lower_find(dev, priv);
+ if (port < 0 || !(n->nud_state & NUD_VALID)) {
+ pr_debug("%s: Neigbour invalid, not updating\n", __func__);
+ return NOTIFY_DONE;
+ }
+
+ net_work = kzalloc(sizeof(*net_work), GFP_ATOMIC);
+ if (!net_work)
+ return NOTIFY_BAD;
+
+ INIT_WORK(&net_work->work, otto_l3_net_event_work_do);
+ net_work->priv = priv;
+
+ net_work->mac = ether_addr_to_u64(n->ha);
+ net_work->gw_addr = *(__be32 *)n->primary_key;
+
+ pr_debug("%s: updating neighbour on port %d, mac %016llx\n",
+ __func__, port, net_work->mac);
+ queue_work(priv->wq, &net_work->work);
+ if (err)
+ netdev_warn(dev, "failed to handle neigh update (err %d)\n", err);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef _OTTO_L3_H
+#define _OTTO_L3_H
+
+#include "rtl-otto.h"
+
+struct otto_l3_route_attr {
+ bool valid;
+ bool hit;
+ bool ttl_dec;
+ bool ttl_check;
+ bool dst_null;
+ bool qos_as;
+ u8 qos_prio;
+ u8 type;
+ u8 action;
+};
+
+struct otto_l3_nexthop {
+ u16 id; /* ID: L3_NEXT_HOP table-index or route-index set in L2_NEXT_HOP */
+ u32 dev_id;
+ u16 port;
+ u16 vid; /* VLAN-ID for L2 table entry (saved from L2-UC entry) */
+ u16 rvid; /* Relay VID/FID for the L2 table entry */
+ u64 mac; /* The MAC address of the entry in the L2_NEXT_HOP table */
+ u16 mac_id;
+ u16 l2_id; /* Index of this next hop forwarding entry in L2 FIB table */
+ u64 gw; /* The gateway MAC address packets are forwarded to */
+ int if_id; /* Interface (into L3_EGR_INTF_IDX) */
+};
+
+struct otto_l3_route {
+ u32 gw_ip; /* IP of the route's gateway */
+ u32 dst_ip; /* IP of the destination net */
+ struct in6_addr dst_ip6;
+ int prefix_len; /* Network prefix len of the destination net */
+ bool is_host_route;
+ int id; /* ID number of this route */
+ struct rhlist_head linkage;
+ u16 switch_mac_id; /* Index into switch's own MACs, RTL839X only */
+ struct otto_l3_nexthop nh;
+ struct pie_rule pr;
+ struct otto_l3_route_attr attr;
+};
+
+static const struct rhashtable_params otto_l3_route_ht_params = {
+ .key_len = sizeof(u32),
+ .key_offset = offsetof(struct otto_l3_route, gw_ip),
+ .head_offset = offsetof(struct otto_l3_route, linkage),
+};
+
+int otto_l3_fib_notifier(struct notifier_block *this, unsigned long event, void *ptr);
+int otto_l3_netevent_notifier(struct notifier_block *this, unsigned long event, void *ptr);
+
+#endif /* _OTTO_L3_H */
*/
#define RTLDSA_COUNTERS_FAST_POLL_INTERVAL (3 * HZ)
+struct otto_l3_route;
+
enum pbvlan_type {
PBVLAN_TYPE_INNER = 0,
PBVLAN_TYPE_OUTER,
u64 mac_mask;
};
-struct rtl83xx_nexthop {
- u16 id; /* ID: L3_NEXT_HOP table-index or route-index set in L2_NEXT_HOP */
- u32 dev_id;
- u16 port;
- u16 vid; /* VLAN-ID for L2 table entry (saved from L2-UC entry) */
- u16 rvid; /* Relay VID/FID for the L2 table entry */
- u64 mac; /* The MAC address of the entry in the L2_NEXT_HOP table */
- u16 mac_id;
- u16 l2_id; /* Index of this next hop forwarding entry in L2 FIB table */
- u64 gw; /* The gateway MAC address packets are forwarded to */
- int if_id; /* Interface (into L3_EGR_INTF_IDX) */
-};
-
struct rtl838x_switch_priv;
struct rtl83xx_flow {
u32 flags;
};
-struct rtl93xx_route_attr {
- bool valid;
- bool hit;
- bool ttl_dec;
- bool ttl_check;
- bool dst_null;
- bool qos_as;
- u8 qos_prio;
- u8 type;
- u8 action;
-};
-
-struct rtl83xx_route {
- u32 gw_ip; /* IP of the route's gateway */
- u32 dst_ip; /* IP of the destination net */
- struct in6_addr dst_ip6;
- int prefix_len; /* Network prefix len of the destination net */
- bool is_host_route;
- int id; /* ID number of this route */
- struct rhlist_head linkage;
- u16 switch_mac_id; /* Index into switch's own MACs, RTL839X only */
- struct rtl83xx_nexthop nh;
- struct pie_rule pr;
- struct rtl93xx_route_attr attr;
-};
-
/**
* struct rtldsa_mirror_config - Mirror configuration for specific group and port
*/
void (*l2_learning_setup)(void);
u32 (*packet_cntr_read)(int counter);
void (*packet_cntr_clear)(int counter);
- void (*route_read)(int idx, struct rtl83xx_route *rt);
- void (*route_write)(int idx, struct rtl83xx_route *rt);
- void (*host_route_write)(int idx, struct rtl83xx_route *rt);
+ void (*route_read)(int idx, struct otto_l3_route *rt);
+ void (*route_write)(int idx, struct otto_l3_route *rt);
+ void (*host_route_write)(int idx, struct otto_l3_route *rt);
int (*l3_setup)(struct rtl838x_switch_priv *priv);
void (*set_l3_nexthop)(int idx, u16 dmac_id, u16 interface);
void (*get_l3_nexthop)(int idx, u16 *dmac_id, u16 *interface);
u64 (*get_l3_egress_mac)(u32 idx);
void (*set_l3_egress_mac)(u32 idx, u64 mac);
- int (*find_l3_slot)(struct rtl83xx_route *rt, bool must_exist);
- int (*route_lookup_hw)(struct rtl83xx_route *rt);
+ int (*find_l3_slot)(struct otto_l3_route *rt, bool must_exist);
+ int (*route_lookup_hw)(struct otto_l3_route *rt);
void (*get_l3_router_mac)(u32 idx, struct rtl93xx_rt_mac *m);
void (*set_l3_router_mac)(u32 idx, struct rtl93xx_rt_mac *m);
void (*set_l3_egress_intf)(int idx, struct rtl838x_l3_intf *intf);
void rtldsa_update_counters_atomically(struct rtl838x_switch_priv *priv, int port);
+
+struct otto_l3_nexthop;
+int rtl83xx_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh);
+int rtl83xx_l2_nexthop_rm(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh);
+
+
extern int rtldsa_max_available_queue[];
extern int rtldsa_default_queue_weights[];
#include <linux/iopoll.h>
#include <net/nexthop.h>
+#include "l3.h"
#include "rtl-otto.h"
#define RTL838X_VLAN_PORT_TAG_STS_UNTAG 0x0
rtl_table_release(r);
}
-static void rtl838x_route_read(int idx, struct rtl83xx_route *rt)
+static void rtl838x_route_read(int idx, struct otto_l3_route *rt)
{
/* Read ROUTING table (2) via register RTL8380_TBL_1 */
struct table_reg *r = rtl_table_get(RTL8380_TBL_1, 2);
rtl_table_release(r);
}
-static void rtl838x_route_write(int idx, struct rtl83xx_route *rt)
+static void rtl838x_route_write(int idx, struct otto_l3_route *rt)
{
/* Access ROUTING table (2) via register RTL8380_TBL_1 */
struct table_reg *r = rtl_table_get(RTL8380_TBL_1, 2);
#include <asm/mach-rtl-otto/mach-rtl-otto.h>
#include <linux/etherdevice.h>
+#include "l3.h"
#include "rtl-otto.h"
#define RTL839X_VLAN_PORT_TAG_STS_UNTAG 0x0
rtl_table_release(r);
}
-static void rtl839x_route_read(int idx, struct rtl83xx_route *rt)
+static void rtl839x_route_read(int idx, struct otto_l3_route *rt)
{
u64 v;
/* Read ROUTING table (2) via register RTL8390_TBL_1 */
rtl_table_release(r);
}
-static void rtl839x_route_write(int idx, struct rtl83xx_route *rt)
+static void rtl839x_route_write(int idx, struct otto_l3_route *rt)
{
u32 v;
#include <linux/etherdevice.h>
#include <linux/inetdevice.h>
+#include "l3.h"
#include "rtl-otto.h"
#define RTL930X_VLAN_PORT_TAG_STS_INTERNAL 0x0
/* Read a prefix route entry from the L3_PREFIX_ROUTE_IPUC table
* We currently only support IPv4 and IPv6 unicast route
*/
-static void rtl930x_route_read(int idx, struct rtl83xx_route *rt)
+static void rtl930x_route_read(int idx, struct otto_l3_route *rt)
{
u32 v, ip4_m;
bool host_route, default_route;
/* Read a host route entry from the table using its index
* We currently only support IPv4 and IPv6 unicast route
*/
-static void rtl930x_host_route_read(int idx, struct rtl83xx_route *rt)
+static void rtl930x_host_route_read(int idx, struct otto_l3_route *rt)
{
u32 v;
/* Read L3_HOST_ROUTE_IPUC table (1) via register RTL9300_TBL_1 */
/* Write a host route entry from the table using its index
* We currently only support IPv4 and IPv6 unicast route
*/
-static void rtl930x_host_route_write(int idx, struct rtl83xx_route *rt)
+static void rtl930x_host_route_write(int idx, struct otto_l3_route *rt)
{
u32 v;
/* Access L3_HOST_ROUTE_IPUC table (1) via register RTL9300_TBL_1 */
/* Look up the index of a prefix route in the routing table CAM for unicast IPv4/6 routes
* using hardware offload.
*/
-static int rtl930x_route_lookup_hw(struct rtl83xx_route *rt)
+static int rtl930x_route_lookup_hw(struct otto_l3_route *rt)
{
u32 ip4_m, v;
struct in6_addr ip6_m;
return -1;
}
-static int rtl930x_find_l3_slot(struct rtl83xx_route *rt, bool must_exist)
+static int rtl930x_find_l3_slot(struct otto_l3_route *rt, bool must_exist)
{
int slot_width, algorithm, addr, idx;
u32 hash;
- struct rtl83xx_route route_entry;
+ struct otto_l3_route route_entry;
/* IPv6 entries take up 3 slots */
slot_width = (rt->attr.type == 0) || (rt->attr.type == 2) ? 1 : 3;
/* Write a prefix route into the routing table CAM at position idx
* Currently only IPv4 and IPv6 unicast routes are supported
*/
-static void rtl930x_route_write(int idx, struct rtl83xx_route *rt)
+static void rtl930x_route_write(int idx, struct otto_l3_route *rt)
{
u32 v, ip4_m;
struct in6_addr ip6_m;