system-linux: add option to configure DSA conduit device
authorChristian Marangi <ansuelsmth@gmail.com>
Thu, 2 Nov 2023 15:34:47 +0000 (16:34 +0100)
committerChristian Marangi <ansuelsmth@gmail.com>
Thu, 9 Nov 2023 13:59:57 +0000 (14:59 +0100)
Device might have multiple CPU port with DSA based switch and OEM
firmware might set specific port to one CPU port (for example WAN) to
sustain full gigabit traffic with the kernel.

To set them iproute2 tool is currently required.
Add support to set the DSA port conduit directly from network config
using netlink. Example:

config device
            option name 'lan1'
            option conduit 'eth1'

Conduit option refer to the CPU port interface. Invalid option will
simply be ignored and won't be applied similar to what iproute2 does.

Option can also be set in board.json by setting the conduit option.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
config.c
config.h
device.c
device.h
system-linux.c
system.h

index f559a3d9ce08f855b3ffdbd5898be1b6079037bf..d4a65169dd98d1a2ca5d5be32d9e1cdfbcd30029 100644 (file)
--- a/config.c
+++ b/config.c
@@ -737,6 +737,24 @@ int config_get_default_gro(const char *ifname)
        return blobmsg_get_bool(cur);
 }
 
+const char *config_get_default_conduit(const char *ifname)
+{
+       struct blob_attr *cur;
+
+       if (!board_netdevs)
+               return NULL;
+
+       cur = config_find_blobmsg_attr(board_netdevs, ifname, BLOBMSG_TYPE_TABLE);
+       if (!cur)
+               return NULL;
+
+       cur = config_find_blobmsg_attr(cur, "conduit", BLOBMSG_TYPE_STRING);
+       if (!cur)
+               return NULL;
+
+       return blobmsg_get_string(cur);
+}
+
 static void
 config_init_board(void)
 {
index e6893159abfe29b2815c14ce270f713bace0d6d0..635a398e9d8a2a184aa8405579ac368f00cd7a6b 100644 (file)
--- a/config.h
+++ b/config.h
@@ -22,5 +22,6 @@ extern bool config_init;
 int config_init_all(void);
 struct ether_addr *config_get_default_macaddr(const char *ifname);
 int config_get_default_gro(const char *ifname);
+const char *config_get_default_conduit(const char *ifname);
 
 #endif
index 09ac11d7a1ae39acc3e0dfff940f908266e3d008..3ad1563b79a2d734b88c5952cfc90e2d43ffc3d8 100644 (file)
--- a/device.c
+++ b/device.c
@@ -73,6 +73,7 @@ static const struct blobmsg_policy dev_attrs[__DEV_ATTR_MAX] = {
        [DEV_ATTR_TXPAUSE] = { .name = "txpause", .type = BLOBMSG_TYPE_BOOL },
        [DEV_ATTR_AUTONEG] = { .name = "autoneg", .type = BLOBMSG_TYPE_BOOL },
        [DEV_ATTR_GRO] = { .name = "gro", .type = BLOBMSG_TYPE_BOOL },
+       [DEV_ATTR_MASTER] = { .name = "conduit", .type = BLOBMSG_TYPE_STRING },
 };
 
 const struct uci_blob_param_list device_attr_list = {
@@ -298,6 +299,7 @@ device_merge_settings(struct device *dev, struct device_settings *n)
        n->txpause = s->flags & DEV_OPT_TXPAUSE ? s->txpause : os->txpause;
        n->autoneg = s->flags & DEV_OPT_AUTONEG ? s->autoneg : os->autoneg;
        n->gro = s->flags & DEV_OPT_GRO ? s->gro : os->gro;
+       n->master_ifindex = s->flags & DEV_OPT_MASTER ? s->master_ifindex : os->master_ifindex;
        n->flags = s->flags | os->flags | os->valid_flags;
 }
 
@@ -553,6 +555,12 @@ device_init_settings(struct device *dev, struct blob_attr **tb)
                s->flags |= DEV_OPT_GRO;
        }
 
+       if ((cur = tb[DEV_ATTR_MASTER])) {
+               char *ifname = blobmsg_get_string(cur);
+               s->master_ifindex = if_nametoindex(ifname);
+               s->flags |= DEV_OPT_MASTER;
+       }
+
        cur = tb[DEV_ATTR_AUTH_VLAN];
        free(dev->config_auth_vlans);
        dev->config_auth_vlans = cur ? blob_memdup(cur) : NULL;
@@ -625,6 +633,7 @@ device_fill_default_settings(struct device *dev)
 {
        struct device_settings *s = &dev->settings;
        struct ether_addr *ea;
+       const char *master;
        int ret;
 
        if (!(s->flags & DEV_OPT_MACADDR)) {
@@ -642,6 +651,14 @@ device_fill_default_settings(struct device *dev)
                        s->flags |= DEV_OPT_GRO;
                }
        }
+
+       if (!(s->flags & DEV_OPT_MASTER)) {
+               master = config_get_default_conduit(dev->ifname);
+               if (master) {
+                       s->master_ifindex = if_nametoindex(master);
+                       s->flags |= DEV_OPT_MASTER;
+               }
+       }
 }
 
 int device_claim(struct device_user *dep)
@@ -1302,6 +1319,13 @@ device_dump_status(struct blob_buf *b, struct device *dev)
 
        if (dev->active) {
                device_merge_settings(dev, &st);
+               if (st.flags & DEV_OPT_MASTER) {
+                       char buf[64], *devname;
+
+                       devname = if_indextoname(st.master_ifindex, buf);
+                       if (devname)
+                               blobmsg_add_string(b, "conduit", devname);
+               }
                if (st.flags & DEV_OPT_MTU)
                        blobmsg_add_u32(b, "mtu", st.mtu);
                if (st.flags & DEV_OPT_MTU6)
index d985f505e1fc23c8d46f6a270fc62ffc276e9314..8bad7fb2d2dd2010fadf4c6069e7a5b40183dc6a 100644 (file)
--- a/device.h
+++ b/device.h
@@ -70,6 +70,7 @@ enum {
        DEV_ATTR_TXPAUSE,
        DEV_ATTR_AUTONEG,
        DEV_ATTR_GRO,
+       DEV_ATTR_MASTER,
        __DEV_ATTR_MAX,
 };
 
@@ -140,6 +141,7 @@ enum {
        DEV_OPT_TXPAUSE                 = (1ULL << 35),
        DEV_OPT_AUTONEG                 = (1ULL << 36),
        DEV_OPT_GRO                     = (1ULL << 37),
+       DEV_OPT_MASTER                  = (1ULL << 38),
 };
 
 /* events broadcasted to all users of a device */
@@ -223,6 +225,7 @@ struct device_settings {
        bool txpause;
        bool autoneg;
        bool gro;
+       int master_ifindex;
 };
 
 struct device_vlan_range {
index 94f262c1ea91069593954a9de4dced88e16a2fcd..515e1b38ecd4fa69eddfaeea703945c0cbd700f8 100644 (file)
@@ -1714,6 +1714,169 @@ int system_vlandev_del(struct device *vlandev)
        return system_link_del(vlandev->ifname);
 }
 
+struct if_get_master_data {
+       int ifindex;
+       int master_ifindex;
+       int pending;
+};
+
+static void if_get_master_dsa_linkinfo_attr(struct if_get_master_data *data,
+                              struct rtattr *attr)
+{
+       struct rtattr *cur;
+       int rem = RTA_PAYLOAD(attr);
+
+       for (cur = RTA_DATA(attr); RTA_OK(cur, rem); cur = RTA_NEXT(cur, rem)) {
+               if (cur->rta_type != IFLA_DSA_MASTER)
+                       continue;
+
+               data->master_ifindex = *(__u32 *)RTA_DATA(cur);
+       }
+}
+
+static void if_get_master_linkinfo_attr(struct if_get_master_data *data,
+                              struct rtattr *attr)
+{
+       struct rtattr *cur;
+       int rem = RTA_PAYLOAD(attr);
+
+       for (cur = RTA_DATA(attr); RTA_OK(cur, rem); cur = RTA_NEXT(cur, rem)) {
+               if (cur->rta_type != IFLA_INFO_KIND && cur->rta_type != IFLA_INFO_DATA)
+                       continue;
+
+               if (cur->rta_type == IFLA_INFO_KIND && strcmp("dsa", (char *)RTA_DATA(cur)))
+                       break;
+
+               if (cur->rta_type == IFLA_INFO_DATA)
+                       if_get_master_dsa_linkinfo_attr(data, cur);
+       }
+}
+
+static int cb_if_get_master_valid(struct nl_msg *msg, void *arg)
+{
+       struct nlmsghdr *nh = nlmsg_hdr(msg);
+       struct ifinfomsg *ifi = NLMSG_DATA(nh);
+       struct if_get_master_data *data = (struct if_get_master_data *)arg;
+       struct rtattr *attr;
+       int rem;
+
+       if (nh->nlmsg_type != RTM_NEWLINK)
+               return NL_SKIP;
+
+       if (ifi->ifi_family != AF_UNSPEC)
+               return NL_SKIP;
+
+       if (ifi->ifi_index != data->ifindex)
+               return NL_SKIP;
+
+       attr = IFLA_RTA(ifi);
+       rem = nh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
+
+       while (RTA_OK(attr, rem)) {
+               if (attr->rta_type == IFLA_LINKINFO)
+                       if_get_master_linkinfo_attr(data, attr);
+
+               attr = RTA_NEXT(attr, rem);
+       }
+
+       return NL_OK;
+}
+
+static int cb_if_get_master_ack(struct nl_msg *msg, void *arg)
+{
+       struct if_get_master_data *data = (struct if_get_master_data *)arg;
+       data->pending = 0;
+       return NL_STOP;
+}
+
+static int cb_if_get_master_error(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg)
+{
+       struct if_get_master_data *data = (struct if_get_master_data *)arg;
+       data->pending = 0;
+       return NL_STOP;
+}
+
+int system_if_get_master_ifindex(struct device *dev)
+{
+       struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT);
+       struct nl_msg *msg;
+       struct ifinfomsg ifi = {
+               .ifi_family = AF_UNSPEC,
+               .ifi_index = 0,
+       };
+       struct if_get_master_data data = {
+               .ifindex = if_nametoindex(dev->ifname),
+               .master_ifindex = -1,
+               .pending = 1,
+       };
+       int ret = -1;
+
+       if (!cb)
+               return ret;
+
+       msg = nlmsg_alloc_simple(RTM_GETLINK, NLM_F_REQUEST);
+       if (!msg)
+               goto out;
+
+       if (nlmsg_append(msg, &ifi, sizeof(ifi), 0) ||
+           nla_put_string(msg, IFLA_IFNAME, dev->ifname))
+               goto free;
+
+       nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_if_get_master_valid, &data);
+       nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, cb_if_get_master_ack, &data);
+       nl_cb_err(cb, NL_CB_CUSTOM, cb_if_get_master_error, &data);
+
+       ret = nl_send_auto_complete(sock_rtnl, msg);
+       if (ret < 0)
+               goto free;
+
+       while (data.pending > 0)
+               nl_recvmsgs(sock_rtnl, cb);
+
+       if (data.master_ifindex >= 0)
+               ret = data.master_ifindex;
+
+free:
+       nlmsg_free(msg);
+out:
+       nl_cb_put(cb);
+       return ret;
+}
+
+static void system_set_master(struct device *dev, int master_ifindex)
+{
+       struct ifinfomsg ifi = { .ifi_family = AF_UNSPEC, };
+       struct nl_msg *nlm;
+
+       nlm = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST);
+       if (!nlm)
+               return;
+
+       nlmsg_append(nlm, &ifi, sizeof(ifi), 0);
+       nla_put_string(nlm, IFLA_IFNAME, dev->ifname);
+
+       struct nlattr *linkinfo = nla_nest_start(nlm, IFLA_LINKINFO);
+       if (!linkinfo)
+               goto failure;
+
+       nla_put_string(nlm, IFLA_INFO_KIND, "dsa");
+       struct nlattr *infodata = nla_nest_start(nlm, IFLA_INFO_DATA);
+       if (!infodata)
+               goto failure;
+
+       nla_put_u32(nlm, IFLA_DSA_MASTER, master_ifindex);
+
+       nla_nest_end(nlm, infodata);
+       nla_nest_end(nlm, linkinfo);
+
+       system_rtnl_call(nlm);
+
+       return;
+
+failure:
+       nlmsg_free(nlm);
+}
+
 static void ethtool_link_mode_clear_bit(__s8 nwords, int nr, __u32 *mask)
 {
        if (nr < 0)
@@ -2033,6 +2196,12 @@ system_if_get_settings(struct device *dev, struct device_settings *s)
                s->gro = ret;
                s->flags |= DEV_OPT_GRO;
        }
+
+       ret = system_if_get_master_ifindex(dev);
+       if (ret >= 0) {
+               s->master_ifindex = ret;
+               s->flags |= DEV_OPT_MASTER;
+       }
 }
 
 void
@@ -2131,6 +2300,8 @@ system_if_apply_settings(struct device *dev, struct device_settings *s, uint64_t
                system_set_drop_unsolicited_na(dev, s->drop_unsolicited_na ? "1" : "0");
        if (apply_mask & DEV_OPT_ARP_ACCEPT)
                system_set_arp_accept(dev, s->arp_accept ? "1" : "0");
+       if (apply_mask & DEV_OPT_MASTER)
+               system_set_master(dev, s->master_ifindex);
        system_set_ethtool_settings(dev, s);
 }
 
index 890966b26e3b14654bfcdef0ff8c9e37e03a76d5..e9661f2eb7640da7ad7164e71bc12c6a0d033eff 100644 (file)
--- a/system.h
+++ b/system.h
@@ -275,6 +275,7 @@ int system_if_resolve(struct device *dev);
 int system_if_dump_info(struct device *dev, struct blob_buf *b);
 int system_if_dump_stats(struct device *dev, struct blob_buf *b);
 struct device *system_if_get_parent(struct device *dev);
+int system_if_get_master_ifindex(struct device *dev);
 bool system_if_force_external(const char *ifname);
 void system_if_apply_settings(struct device *dev, struct device_settings *s,
                              uint64_t apply_mask);