From aae0e68909bf2ef1b60d18c54105ab8b9c67c5a2 Mon Sep 17 00:00:00 2001 From: Codrin Ciubotariu Date: Tue, 15 Dec 2015 15:21:06 +0200 Subject: [PATCH] drivers: net: vsc9953: Add LAG support You can now configure LAG on VSC9953's ports using the command: ethsw [port ] aggr {[help] | show | } A port must belong to a single LAG. By default, a port belongs to a LAG equal to the port's number. For each frame, a hash will be calculated based on Source/Destination MAC addresses, Source/Destination IP(v4/v6) addresses, Source/Destination ports. This hash will be used to select a single egress port from LAG. This also assures that frames from the same flow will always have the same egress port. Signed-off-by: Codrin Ciubotariu Acked-by: Joe Hershberger --- common/cmd_ethsw.c | 74 +++++++++++ doc/README.t1040-l2switch | 2 + drivers/net/vsc9953.c | 331 +++++++++++++++++++++++++++++++++++++++++++++- include/ethsw.h | 6 + include/vsc9953.h | 18 +++ 5 files changed, 430 insertions(+), 1 deletion(-) diff --git a/common/cmd_ethsw.c b/common/cmd_ethsw.c index a5ed555..491cb8e 100644 --- a/common/cmd_ethsw.c +++ b/common/cmd_ethsw.c @@ -114,6 +114,17 @@ static int ethsw_ingr_fltr_help_key_func(struct ethsw_command_def *parsed_cmd) return CMD_RET_SUCCESS; } +#define ETHSW_PORT_AGGR_HELP "ethsw [port ] aggr" \ +" { [help] | show | } " \ +"- get/set LAG group for a port" + +static int ethsw_port_aggr_help_key_func(struct ethsw_command_def *parsed_cmd) +{ + printf(ETHSW_PORT_AGGR_HELP"\n"); + + return CMD_RET_SUCCESS; +} + static struct keywords_to_function { enum ethsw_keyword_id cmd_keyword[ETHSW_MAX_CMD_PARAMS]; int cmd_func_offset; @@ -532,6 +543,39 @@ static struct keywords_to_function { .cmd_func_offset = offsetof(struct ethsw_command_func, port_ingr_filt_set), .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_aggr, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_port_aggr_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_aggr, + ethsw_id_help, + ethsw_id_key_end, + }, + .cmd_func_offset = -1, + .keyword_function = ðsw_port_aggr_help_key_func, + }, { + .cmd_keyword = { + ethsw_id_aggr, + ethsw_id_show, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_aggr_show), + .keyword_function = NULL, + }, { + .cmd_keyword = { + ethsw_id_aggr, + ethsw_id_aggr_no, + ethsw_id_key_end, + }, + .cmd_func_offset = offsetof(struct ethsw_command_func, + port_aggr_set), + .keyword_function = NULL, }, }; @@ -576,6 +620,9 @@ static int keyword_match_pvid(enum ethsw_keyword_id key_id, int argc, static int keyword_match_mac_addr(enum ethsw_keyword_id key_id, int argc, char *const argv[], int *argc_nr, struct ethsw_command_def *parsed_cmd); +static int keyword_match_aggr(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd); /* * Define properties for each keyword; @@ -661,6 +708,9 @@ struct keyword_def { }, { .keyword_name = "filtering", .match = &keyword_match_gen, + }, { + .keyword_name = "aggr", + .match = &keyword_match_aggr, }, }; @@ -826,6 +876,28 @@ static int keyword_match_mac_addr(enum ethsw_keyword_id key_id, int argc, return 1; } +/* Function used to match the command's aggregation number */ +static int keyword_match_aggr(enum ethsw_keyword_id key_id, int argc, + char *const argv[], int *argc_nr, + struct ethsw_command_def *parsed_cmd) +{ + unsigned long val; + + if (!keyword_match_gen(key_id, argc, argv, argc_nr, parsed_cmd)) + return 0; + + if (*argc_nr + 1 >= argc) + return 1; + + if (strict_strtoul(argv[*argc_nr + 1], 10, &val) != -EINVAL) { + parsed_cmd->aggr_grp = val; + (*argc_nr)++; + parsed_cmd->cmd_to_keywords[*argc_nr] = ethsw_id_aggr_no; + } + + return 1; +} + /* Finds optional keywords and modifies *argc_va to skip them */ static void cmd_keywords_opt_check(const struct ethsw_command_def *parsed_cmd, int *argc_val) @@ -984,6 +1056,7 @@ static void command_def_init(struct ethsw_command_def *parsed_cmd) parsed_cmd->port = ETHSW_CMD_PORT_ALL; parsed_cmd->vid = ETHSW_CMD_VLAN_ALL; + parsed_cmd->aggr_grp = ETHSW_CMD_AGGR_GRP_NONE; parsed_cmd->cmd_function = NULL; /* We initialize the MAC address with the Broadcast address */ @@ -1024,4 +1097,5 @@ U_BOOT_CMD(ethsw, ETHSW_MAX_CMD_PARAMS, 0, do_ethsw, ETHSW_EGR_VLAN_TAG_HELP"\n" ETHSW_VLAN_FDB_HELP"\n" ETHSW_PORT_INGR_FLTR_HELP"\n" + ETHSW_PORT_AGGR_HELP"\n" ); diff --git a/doc/README.t1040-l2switch b/doc/README.t1040-l2switch index 4e0f418..6f03de2 100644 --- a/doc/README.t1040-l2switch +++ b/doc/README.t1040-l2switch @@ -31,6 +31,7 @@ Commands supported - Port-based VLAN - Private/Shared VLAN learning - VLAN ingress filtering + - Port LAG Commands syntax ethsw [port ] { enable | disable | show } - enable/disable a port; show a port's configuration @@ -45,6 +46,7 @@ ethsw [port ] egress tag { [help] | show | pvid | classified } - config Tag's VID could be the frame's classified VID or the PVID of the port ethsw vlan fdb { [help] | show | shared | private } - make VLAN learning shared or private ethsw [port ] ingress filtering { [help] | show | enable | disable } - enable/disable VLAN ingress filtering on port +ethsw [port ] aggr { [help] | show | } - get/set LAG group for a port => ethsw show Port Status Link Speed Duplex diff --git a/drivers/net/vsc9953.c b/drivers/net/vsc9953.c index 160f478..44afe14 100644 --- a/drivers/net/vsc9953.c +++ b/drivers/net/vsc9953.c @@ -469,6 +469,47 @@ static void vsc9953_vlan_ingr_fltr_learn_drop(int enable) clrbits_le32(&l2ana_reg->ana.adv_learn, VSC9953_VLAN_CHK); } +enum aggr_code_mode { + AGGR_CODE_RAND = 0, + AGGR_CODE_ALL, /* S/D MAC, IPv4 S/D IP, IPv6 Flow Label, S/D PORT */ +}; + +/* Set aggregation code generation mode */ +static int vsc9953_aggr_code_set(enum aggr_code_mode ac) +{ + int rc; + struct vsc9953_analyzer *l2ana_reg; + + l2ana_reg = (struct vsc9953_analyzer *)(VSC9953_OFFSET + + VSC9953_ANA_OFFSET); + + switch (ac) { + case AGGR_CODE_RAND: + clrsetbits_le32(&l2ana_reg->common.aggr_cfg, + VSC9953_AC_DMAC_ENA | VSC9953_AC_SMAC_ENA | + VSC9953_AC_IP6_LBL_ENA | + VSC9953_AC_IP6_TCPUDP_ENA | + VSC9953_AC_IP4_SIPDIP_ENA | + VSC9953_AC_IP4_TCPUDP_ENA, VSC9953_AC_RND_ENA); + rc = 0; + break; + case AGGR_CODE_ALL: + clrsetbits_le32(&l2ana_reg->common.aggr_cfg, VSC9953_AC_RND_ENA, + VSC9953_AC_DMAC_ENA | VSC9953_AC_SMAC_ENA | + VSC9953_AC_IP6_LBL_ENA | + VSC9953_AC_IP6_TCPUDP_ENA | + VSC9953_AC_IP4_SIPDIP_ENA | + VSC9953_AC_IP4_TCPUDP_ENA); + rc = 0; + break; + default: + /* unknown mode for aggregation code */ + rc = -EINVAL; + } + + return rc; +} + /* Egress untag modes of a VSC9953 port */ enum egress_untag_mode { EGRESS_UNTAG_ALL = 0, @@ -1493,6 +1534,224 @@ static int vsc9953_port_ingress_filtering_get(int port_no) return !!(val & (1 << port_no)); } +/* Get the aggregation group of a port */ +static int vsc9953_port_aggr_grp_get(int port_no, int *aggr_grp) +{ + u32 val; + struct vsc9953_analyzer *l2ana_reg; + + if (!VSC9953_PORT_CHECK(port_no)) + return -EINVAL; + + l2ana_reg = (struct vsc9953_analyzer *)(VSC9953_OFFSET + + VSC9953_ANA_OFFSET); + + val = in_le32(&l2ana_reg->port[port_no].port_cfg); + *aggr_grp = bitfield_extract_by_mask(val, + VSC9953_PORT_CFG_PORTID_MASK); + + return 0; +} + +static void vsc9953_aggr_grp_members_get(int aggr_grp, + u8 aggr_membr[VSC9953_MAX_PORTS]) +{ + int port_no; + int aggr_membr_grp; + + for (port_no = 0; port_no < VSC9953_MAX_PORTS; port_no++) { + aggr_membr[port_no] = 0; + + if (vsc9953_port_aggr_grp_get(port_no, &aggr_membr_grp)) + continue; + + if (aggr_grp == aggr_membr_grp) + aggr_membr[port_no] = 1; + } +} + +static void vsc9953_update_dest_members_masks(int port_no, u32 membr_bitfld_old, + u32 membr_bitfld_new) +{ + int i; + u32 pgid; + struct vsc9953_analyzer *l2ana_reg; + + l2ana_reg = (struct vsc9953_analyzer *)(VSC9953_OFFSET + + VSC9953_ANA_OFFSET); + + /* + * NOTE: Only the unicast destination masks are updated, since + * we do not support for now Layer-2 multicast entries + */ + for (i = 0; i < VSC9953_MAX_PORTS; i++) { + if (i == port_no) { + clrsetbits_le32(&l2ana_reg->port_id_tbl.port_grp_id[i], + VSC9953_PGID_PORT_MASK, + membr_bitfld_new); + continue; + } + + pgid = in_le32(&l2ana_reg->port_id_tbl.port_grp_id[i]); + if ((u32)(1 << i) & membr_bitfld_old & VSC9953_PGID_PORT_MASK) + pgid &= ~((u32)(1 << port_no)); + if ((u32)(1 << i) & membr_bitfld_new & VSC9953_PGID_PORT_MASK) + pgid |= ((u32)(1 << port_no)); + + out_le32(&l2ana_reg->port_id_tbl.port_grp_id[i], pgid); + } +} + +static void vsc9953_update_source_members_masks(int port_no, + u32 membr_bitfld_old, + u32 membr_bitfld_new) +{ + int i; + int index; + u32 pgid; + struct vsc9953_analyzer *l2ana_reg; + + l2ana_reg = (struct vsc9953_analyzer *)(VSC9953_OFFSET + + VSC9953_ANA_OFFSET); + + for (i = 0; i < VSC9953_MAX_PORTS + 1; i++) { + index = PGID_SRC_START + i; + pgid = in_le32(&l2ana_reg->port_id_tbl.port_grp_id[index]); + if (i == port_no) { + pgid = (pgid | VSC9953_PGID_PORT_MASK) & + ~membr_bitfld_new; + out_le32(&l2ana_reg->port_id_tbl.port_grp_id[index], + pgid); + continue; + } + + if ((u32)(1 << i) & membr_bitfld_old & VSC9953_PGID_PORT_MASK) + pgid |= (u32)(1 << port_no); + + if ((u32)(1 << i) & membr_bitfld_new & VSC9953_PGID_PORT_MASK) + pgid &= ~(u32)(1 << port_no); + out_le32(&l2ana_reg->port_id_tbl.port_grp_id[index], pgid); + } +} + +static u32 vsc9953_aggr_mask_get_next(u32 aggr_mask, u32 member_bitfield) +{ + if (!member_bitfield) + return 0; + + if (!(aggr_mask & VSC9953_PGID_PORT_MASK)) + aggr_mask = 1; + else + aggr_mask <<= 1; + + while (!(aggr_mask & member_bitfield)) { + aggr_mask <<= 1; + if (!(aggr_mask & VSC9953_PGID_PORT_MASK)) + aggr_mask = 1; + } + + return aggr_mask; +} + +static void vsc9953_update_aggr_members_masks(int port_no, u32 membr_bitfld_old, + u32 membr_bitfld_new) +{ + int i; + u32 pgid; + u32 aggr_mask_old = 0; + u32 aggr_mask_new = 0; + struct vsc9953_analyzer *l2ana_reg; + + l2ana_reg = (struct vsc9953_analyzer *)(VSC9953_OFFSET + + VSC9953_ANA_OFFSET); + + /* Update all the PGID aggregation masks */ + for (i = PGID_AGGR_START; i < PGID_SRC_START; i++) { + pgid = in_le32(&l2ana_reg->port_id_tbl.port_grp_id[i]); + + aggr_mask_old = vsc9953_aggr_mask_get_next(aggr_mask_old, + membr_bitfld_old); + pgid = (pgid & ~membr_bitfld_old) | aggr_mask_old; + + aggr_mask_new = vsc9953_aggr_mask_get_next(aggr_mask_new, + membr_bitfld_new); + pgid = (pgid & ~membr_bitfld_new) | aggr_mask_new; + + out_le32(&l2ana_reg->port_id_tbl.port_grp_id[i], pgid); + } +} + +static u32 vsc9953_aggr_membr_bitfield_get(u8 member[VSC9953_MAX_PORTS]) +{ + int i; + u32 member_bitfield = 0; + + for (i = 0; i < VSC9953_MAX_PORTS; i++) { + if (member[i]) + member_bitfield |= 1 << i; + } + member_bitfield &= VSC9953_PGID_PORT_MASK; + + return member_bitfield; +} + +static void vsc9953_update_members_masks(int port_no, + u8 member_old[VSC9953_MAX_PORTS], + u8 member_new[VSC9953_MAX_PORTS]) +{ + u32 membr_bitfld_old = vsc9953_aggr_membr_bitfield_get(member_old); + u32 membr_bitfld_new = vsc9953_aggr_membr_bitfield_get(member_new); + + vsc9953_update_dest_members_masks(port_no, membr_bitfld_old, + membr_bitfld_new); + vsc9953_update_source_members_masks(port_no, membr_bitfld_old, + membr_bitfld_new); + vsc9953_update_aggr_members_masks(port_no, membr_bitfld_old, + membr_bitfld_new); +} + +/* Set the aggregation group of a port */ +static int vsc9953_port_aggr_grp_set(int port_no, int aggr_grp) +{ + u8 aggr_membr_old[VSC9953_MAX_PORTS]; + u8 aggr_membr_new[VSC9953_MAX_PORTS]; + int rc; + int aggr_grp_old; + u32 val; + struct vsc9953_analyzer *l2ana_reg; + + if (!VSC9953_PORT_CHECK(port_no) || !VSC9953_PORT_CHECK(aggr_grp)) + return -EINVAL; + + l2ana_reg = (struct vsc9953_analyzer *)(VSC9953_OFFSET + + VSC9953_ANA_OFFSET); + + rc = vsc9953_port_aggr_grp_get(port_no, &aggr_grp_old); + if (rc) + return rc; + + /* get all the members of the old aggregation group */ + vsc9953_aggr_grp_members_get(aggr_grp_old, aggr_membr_old); + + /* get all the members of the same aggregation group */ + vsc9953_aggr_grp_members_get(aggr_grp, aggr_membr_new); + + /* add current port as member to the new aggregation group */ + aggr_membr_old[port_no] = 0; + aggr_membr_new[port_no] = 1; + + /* update masks */ + vsc9953_update_members_masks(port_no, aggr_membr_old, aggr_membr_new); + + /* Change logical port number */ + val = in_le32(&l2ana_reg->port[port_no].port_cfg); + val = bitfield_replace_by_mask(val, + VSC9953_PORT_CFG_PORTID_MASK, aggr_grp); + out_le32(&l2ana_reg->port[port_no].port_cfg, val); + + return 0; +} + static int vsc9953_port_status_key_func(struct ethsw_command_def *parsed_cmd) { int i; @@ -2083,6 +2342,72 @@ static int vsc9953_ingr_fltr_set_key_func(struct ethsw_command_def *parsed_cmd) return CMD_RET_SUCCESS; } +static int vsc9953_port_aggr_show_key_func(struct ethsw_command_def *parsed_cmd) +{ + int i; + int aggr_grp; + + if (parsed_cmd->port != ETHSW_CMD_PORT_ALL) { + if (!VSC9953_PORT_CHECK(parsed_cmd->port)) { + printf("Invalid port number: %d\n", parsed_cmd->port); + return CMD_RET_FAILURE; + } + + if (vsc9953_port_aggr_grp_get(parsed_cmd->port, &aggr_grp)) + return CMD_RET_FAILURE; + printf("%7s %10s\n", "Port", "Aggr grp"); + printf("%7d %10d\n", parsed_cmd->port, aggr_grp); + } else { + printf("%7s %10s\n", "Port", "Aggr grp"); + for (i = 0; i < VSC9953_MAX_PORTS; i++) { + if (vsc9953_port_aggr_grp_get(i, &aggr_grp)) + continue; + printf("%7d %10d\n", i, aggr_grp); + } + } + + return CMD_RET_SUCCESS; +} + +static int vsc9953_port_aggr_set_key_func(struct ethsw_command_def *parsed_cmd) +{ + int i; + + /* Aggregation group number should be set in parsed_cmd->aggr_grp */ + if (parsed_cmd->aggr_grp == ETHSW_CMD_AGGR_GRP_NONE) { + printf("Please set an aggregation group value\n"); + return CMD_RET_FAILURE; + } + + if (!VSC9953_PORT_CHECK(parsed_cmd->aggr_grp)) { + printf("Invalid aggregation group number: %d\n", + parsed_cmd->aggr_grp); + return CMD_RET_FAILURE; + } + + if (parsed_cmd->port != ETHSW_CMD_PORT_ALL) { + if (!VSC9953_PORT_CHECK(parsed_cmd->port)) { + printf("Invalid port number: %d\n", parsed_cmd->port); + return CMD_RET_FAILURE; + } + if (vsc9953_port_aggr_grp_set(parsed_cmd->port, + parsed_cmd->aggr_grp)) { + printf("Port %d: failed to set aggr group %d\n", + parsed_cmd->port, parsed_cmd->aggr_grp); + } + } else { + for (i = 0; i < VSC9953_MAX_PORTS; i++) { + if (vsc9953_port_aggr_grp_set(i, + parsed_cmd->aggr_grp)) { + printf("Port %d: failed to set aggr group %d\n", + i, parsed_cmd->aggr_grp); + } + } + } + + return CMD_RET_SUCCESS; +} + static struct ethsw_command_func vsc9953_cmd_func = { .ethsw_name = "L2 Switch VSC9953", .port_enable = &vsc9953_port_status_key_func, @@ -2107,7 +2432,9 @@ static struct ethsw_command_func vsc9953_cmd_func = { .vlan_learn_show = &vsc9953_vlan_learn_show_key_func, .vlan_learn_set = &vsc9953_vlan_learn_set_key_func, .port_ingr_filt_show = &vsc9953_ingr_fltr_show_key_func, - .port_ingr_filt_set = &vsc9953_ingr_fltr_set_key_func + .port_ingr_filt_set = &vsc9953_ingr_fltr_set_key_func, + .port_aggr_show = &vsc9953_port_aggr_show_key_func, + .port_aggr_set = &vsc9953_port_aggr_set_key_func, }; #endif /* CONFIG_CMD_ETHSW */ @@ -2138,6 +2465,8 @@ void vsc9953_default_configuration(void) vsc9953_vlan_table_membership_all_set(1, 1); vsc9953_vlan_ingr_fltr_learn_drop(1); vsc9953_port_all_vlan_egress_untagged_set(EGRESS_UNTAG_PVID_AND_ZERO); + if (vsc9953_aggr_code_set(AGGR_CODE_ALL)) + debug("VSC9953: failed to set default aggregation code mode\n"); } void vsc9953_init(bd_t *bis) diff --git a/include/ethsw.h b/include/ethsw.h index 2d3c12a..25f358d 100644 --- a/include/ethsw.h +++ b/include/ethsw.h @@ -12,6 +12,7 @@ #define ETHSW_MAX_CMD_PARAMS 20 #define ETHSW_CMD_PORT_ALL -1 #define ETHSW_CMD_VLAN_ALL -1 +#define ETHSW_CMD_AGGR_GRP_NONE -1 /* IDs used to track keywords in a command */ enum ethsw_keyword_id { @@ -41,6 +42,7 @@ enum ethsw_keyword_id { ethsw_id_private, ethsw_id_ingress, ethsw_id_filtering, + ethsw_id_aggr, ethsw_id_count, /* keep last */ }; @@ -50,6 +52,7 @@ enum ethsw_keyword_opt_id { ethsw_id_pvid_no, ethsw_id_add_del_no, ethsw_id_add_del_mac, + ethsw_id_aggr_no, ethsw_id_count_all, /* keep last */ }; @@ -58,6 +61,7 @@ struct ethsw_command_def { int cmd_keywords_nr; int port; int vid; + int aggr_grp; uchar ethaddr[6]; int (*cmd_function)(struct ethsw_command_def *parsed_cmd); }; @@ -88,6 +92,8 @@ struct ethsw_command_func { int (*vlan_learn_set)(struct ethsw_command_def *parsed_cmd); int (*port_ingr_filt_show)(struct ethsw_command_def *parsed_cmd); int (*port_ingr_filt_set)(struct ethsw_command_def *parsed_cmd); + int (*port_aggr_show)(struct ethsw_command_def *parsed_cmd); + int (*port_aggr_set)(struct ethsw_command_def *parsed_cmd); }; int ethsw_define_functions(const struct ethsw_command_func *cmd_func); diff --git a/include/vsc9953.h b/include/vsc9953.h index 35fcbb5..a2d4554 100644 --- a/include/vsc9953.h +++ b/include/vsc9953.h @@ -126,6 +126,7 @@ #define VSC9953_PORT_CFG_LEARN_AUTO 0x00000100 #define VSC9953_PORT_CFG_LEARN_CPU 0x00000200 #define VSC9953_PORT_CFG_LEARN_DROP 0x00000400 +#define VSC9953_PORT_CFG_PORTID_MASK 0x0000003c /* Macros for vsc9953_qsys_sys.switch_port_mode register */ #define VSC9953_PORT_ENA 0x00002000 @@ -156,6 +157,19 @@ /* Macros for vsc9953_ana_ana_tables.mach_data register */ #define VSC9953_MACHDATA_VID_MASK 0x1fff0000 +/* Macros for vsc9953_ana_common.aggr_cfg register */ +#define VSC9953_AC_RND_ENA 0x00000080 +#define VSC9953_AC_DMAC_ENA 0x00000040 +#define VSC9953_AC_SMAC_ENA 0x00000020 +#define VSC9953_AC_IP6_LBL_ENA 0x00000010 +#define VSC9953_AC_IP6_TCPUDP_ENA 0x00000008 +#define VSC9953_AC_IP4_SIPDIP_ENA 0x00000004 +#define VSC9953_AC_IP4_TCPUDP_ENA 0x00000002 +#define VSC9953_AC_MASK 0x000000fe + +/* Macros for vsc9953_ana_pgid.port_grp_id[] registers */ +#define VSC9953_PGID_PORT_MASK 0x000003ff + #define VSC9953_MAX_PORTS 10 #define VSC9953_PORT_CHECK(port) \ (((port) < 0 || (port) >= VSC9953_MAX_PORTS) ? 0 : 1) @@ -239,6 +253,10 @@ struct vsc9953_ana_ana { u32 port_mode[12]; }; +#define PGID_DST_START 0 +#define PGID_AGGR_START 64 +#define PGID_SRC_START 80 + struct vsc9953_ana_pgid { u32 port_grp_id[91]; };