From 2dc7f450f3a2adbf1c2851442bfa31ee331f9a9b Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Thu, 2 Nov 2023 16:34:47 +0100 Subject: [PATCH] system-linux: add option to configure DSA conduit device 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 --- config.c | 18 ++++++ config.h | 1 + device.c | 24 +++++++ device.h | 3 + system-linux.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++++ system.h | 1 + 6 files changed, 218 insertions(+) diff --git a/config.c b/config.c index f559a3d..d4a6516 100644 --- 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) { diff --git a/config.h b/config.h index e689315..635a398 100644 --- 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 diff --git a/device.c b/device.c index 09ac11d..3ad1563 100644 --- 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) diff --git a/device.h b/device.h index d985f50..8bad7fb 100644 --- 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 { diff --git a/system-linux.c b/system-linux.c index 94f262c..515e1b3 100644 --- a/system-linux.c +++ b/system-linux.c @@ -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); } diff --git a/system.h b/system.h index 890966b..e9661f2 100644 --- 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); -- 2.30.2