diff --git a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c index acde80f5..43e35ae1 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c +++ b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.c @@ -1337,6 +1337,199 @@ static int ether_start_xmit(struct sk_buff *skb, struct net_device *ndev) return NETDEV_TX_OK; } +/** + * ether_prepare_mc_list- function to configure the multicast + * address in device. + * + * @dev: Pointer to net_device structure. + * + * Algorithm: + * This function collects all the multicast addresses and updates the + * device. + * + * Dependencies: MAC and PHY need to be initialized. + * + * Protection: None. + * + * Return: OSI_PERFECT_FILTER_MODE - perfect filtering is seleted + * OSI_HASH_FILTER_MODE - if hash filtering is seleted. + */ +static int ether_prepare_mc_list(struct net_device *dev) +{ + struct ether_priv_data *pdata = netdev_priv(dev); + struct osi_core_priv_data *osi_core = pdata->osi_core; + struct netdev_hw_addr *ha; + int ret = OSI_PERFECT_FILTER_MODE, i = 1; + int cnt; + + if (pdata->l2_filtering_mode == OSI_HASH_FILTER_MODE) { + dev_err(pdata->dev, + "select HASH FILTERING for mc addresses is not supported in SW\n"); + /* only perfect filter is supported */ + } else { + dev_dbg(pdata->dev, + "select PERFECT FILTERING for mc addresses, mc_count = %d, num_mac_addr_regs = %d\n", + netdev_mc_count(dev), + pdata->num_mac_addr_regs); + /* Clear previously set filters */ + for (cnt = 1; cnt <= pdata->last_uc_filter_index; cnt++) { + osi_update_mac_addr_low_high_reg(osi_core, cnt, NULL); + } + + netdev_for_each_mc_addr(ha, dev) { + dev_dbg(pdata->dev, + "mc addr[%d] = %#x:%#x:%#x:%#x:%#x:%#x\n", + i, + ha->addr[0], ha->addr[1], ha->addr[2], + ha->addr[3], ha->addr[4], ha->addr[5]); + osi_update_mac_addr_low_high_reg(osi_core, i, ha->addr); + if (i == EQOS_MAX_MAC_ADDRESS_FILTER - 1) { + dev_err(pdata->dev, "Configured max number of supported MAC, ignoring it\n"); + break; + } + i++; + } + /* preserve last MC filter index to passon to UC */ + pdata->last_mc_filter_index = i - 1; + } + + return ret; +} + +/** + * ether_prepare_uc_list- function to configure the unicast address + * in device. + * + * @dev - pointer to net_device structure. + * + * Algorithm: + * This function collects all the unicast addresses and updates the + * device. + * + * Dependencies: MAC and PHY need to be initialized. + * + * Protection: None. + * + * Return: OSI_PERFECT_FILTER_MODE - perfect filtering is seleted + * OSI_HASH_FILTER_MODE - if hash filtering is seleted. + */ +static int ether_prepare_uc_list(struct net_device *dev) +{ + struct ether_priv_data *pdata = netdev_priv(dev); + struct osi_core_priv_data *osi_core = pdata->osi_core; + int i = pdata->last_mc_filter_index + 1; + int ret = OSI_PERFECT_FILTER_MODE; + struct netdev_hw_addr *ha; + int cnt; + + if (pdata->l2_filtering_mode == OSI_HASH_FILTER_MODE) { + dev_err(pdata->dev, + "select HASH FILTERING for uc addresses not Supported in SW\n"); + /* only perfect filter is supported */ + } else { + dev_dbg(pdata->dev, + "select PERFECT FILTERING for uc addresses: uc_count = %d\n", + netdev_uc_count(dev)); + /* Clear previously set filters */ + for (cnt = pdata->last_mc_filter_index + 1; + cnt <= pdata->last_uc_filter_index; cnt++) { + osi_update_mac_addr_low_high_reg(osi_core, cnt, NULL); + } + + netdev_for_each_uc_addr(ha, dev) { + dev_dbg(pdata->dev, + "uc addr[%d] = %#x:%#x:%#x:%#x:%#x:%#x\n", + i, ha->addr[0], ha->addr[1], ha->addr[2], + ha->addr[3], ha->addr[4], ha->addr[5]); + osi_update_mac_addr_low_high_reg(osi_core, i, ha->addr); + if (i == EQOS_MAX_MAC_ADDRESS_FILTER - 1) { + dev_err(pdata->dev, "Already MAX MAC added\n"); + break; + } + i++; + } + pdata->last_uc_filter_index = i - 1; + } + + return ret; +} + +/** + * ether_set_rx_mode - This function is used to set RX mode. + * + * @dev - pointer to net_device structure. + * + * Algorithm: + * Based on Network interface flag, MAC registers are programmed to set + * mode + * + * Dependencies: MAC and PHY need to be initialized. + * + * Protection: Spinlock is used for protection. + * + * Return: None + */ +static void ether_set_rx_mode(struct net_device *dev) +{ + struct ether_priv_data *pdata = netdev_priv(dev); + struct osi_core_priv_data *osi_core = pdata->osi_core; + struct osi_filter filter = {0}; + int mode, ret; + + spin_lock_bh(&pdata->lock); + + if ((dev->flags & IFF_PROMISC) == IFF_PROMISC) { + dev_dbg(pdata->dev, "enabling Promiscuous mode\n"); + filter.pr_mode = OSI_ENABLE; + } else if ((dev->flags & IFF_ALLMULTI) == IFF_ALLMULTI) { + dev_dbg(pdata->dev, "pass all multicast pkt\n"); + filter.pm_mode = OSI_ENABLE; + } else if (!netdev_mc_empty(dev)) { + dev_dbg(pdata->dev, "pass list of multicast pkt\n"); + if (netdev_mc_count(dev) > (pdata->num_mac_addr_regs - 1)) { + /* switch to PROMISCUOUS mode */ + filter.pr_mode = OSI_ENABLE; + } else { + mode = ether_prepare_mc_list(dev); + if (mode == OSI_HASH_FILTER_MODE) { + /* Hash filtering for multicast */ + filter.hmc_mode = OSI_ENABLE; + } else { + /* Perfect filtering for multicast */ + filter.hmc_mode = OSI_DISABLE; + filter.hpf_mode = OSI_ENABLE; + } + } + } else { + pdata->last_mc_filter_index = 0; + } + + /* Handle multiple unicast addresses */ + if (netdev_uc_count(dev) > (pdata->num_mac_addr_regs - 1)) { + /* switch to PROMISCUOUS mode */ + filter.pr_mode = OSI_ENABLE; + } else if (!netdev_uc_empty(dev)) { + mode = ether_prepare_uc_list(dev); + if (mode == OSI_HASH_FILTER_MODE) { + /* Hash filtering for unicast */ + filter.huc_mode = OSI_ENABLE; + } else { + /* Perfect filtering for unicast */ + filter.huc_mode = OSI_DISABLE; + filter.hpf_mode = OSI_ENABLE; + } + } else { + pdata->last_uc_filter_index = pdata->last_mc_filter_index; + } + + ret = osi_config_mac_pkt_filter_reg(osi_core, filter); + if (ret != 0) { + dev_err(pdata->dev, "osi_config_mac_pkt_filter_reg failed\n"); + } + + spin_unlock_bh(&pdata->lock); +} + /** * ether_ioctl - network stack IOCTL hook to driver * @ndev: network device structure @@ -1356,12 +1549,15 @@ static int ether_start_xmit(struct sk_buff *skb, struct net_device *ndev) static int ether_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { int ret = -EOPNOTSUPP; + struct ether_priv_data *pdata = netdev_priv(dev); if (!dev || !rq) { + dev_err(pdata->dev, "%s: Invalid arg\n", __func__); return -EINVAL; } if (!netif_running(dev)) { + dev_err(pdata->dev, "%s: Interface not up\n", __func__); return -EINVAL; } @@ -1507,6 +1703,83 @@ static int ether_set_features(struct net_device *ndev, netdev_features_t feat) return ret; } +/** + * ether_vlan_rx_add_vid- Add VLAN ID. This function is invoked by upper + * layer when a new VLAN id is registered. This function updates the HW + * filter with new VLAN id. New vlan id can be added with vconfig - + * vconfig add + * + * @ndev: Network device structure + * @proto: VLAN proto VLAN_PROTO_8021Q = 0 VLAN_PROTO_8021AD = 1 + * @vid: VLAN ID. + * + * Algorithm: + * 1) Check for hash or perfect filtering. + * 2) invoke osi call accordingly. + * + * Dependencies: Ethernet interface should be up + * + * Protection: None. + * + * Return: 0 - success Negative - failure + */ +static int ether_vlan_rx_add_vid(struct net_device *ndev, __be16 vlan_proto, + u16 vid) +{ + struct ether_priv_data *pdata = netdev_priv(ndev); + struct osi_core_priv_data *osi_core = pdata->osi_core; + int ret = -1; + + if (pdata->vlan_hash_filtering == OSI_HASH_FILTER_MODE) { + dev_err(pdata->dev, + "HASH FILTERING for VLAN tag is not supported in SW\n"); + } else { + ret = osi_update_vlan_id(osi_core, vid); + } + + return ret; +} + +/** + * ether_vlan_rx_kill_vid- Remove VLAN ID. This function is invoked by + * upper layer when a new VALN id is removed. This function updates the HW + * filter. vlan id can be removed with vconfig - + * vconfig rem + * + * @ndev: Network device structure + * @vlan_proto: VLAN proto VLAN_PROTO_8021Q = 0 VLAN_PROTO_8021AD = 1 + * @vid: VLAN ID. + * + * Algorithm: + * 1) Check for hash or perfect filtering. + * 2) invoke osi call accordingly. + * + * Dependencies: Ethernet interface should be up + * + * Protection: None. + * + * Return: 0 - success Negative - failure + */ +static int ether_vlan_rx_kill_vid(struct net_device *ndev, __be16 vlan_proto, + u16 vid) +{ + struct ether_priv_data *pdata = netdev_priv(ndev); + struct osi_core_priv_data *osi_core = pdata->osi_core; + int ret = -1; + + if (pdata->vlan_hash_filtering == OSI_HASH_FILTER_MODE) { + dev_err(pdata->dev, + "HASH FILTERING for VLAN tag is not supported in SW\n"); + } else { + /* By default, receive only VLAN pkt with VID = 1 because + * writing 0 will pass all VLAN pkt + */ + ret = osi_update_vlan_id(osi_core, 0x1U); + } + + return ret; +} + static const struct net_device_ops ether_netdev_ops = { .ndo_open = ether_open, .ndo_stop = ether_close, @@ -1516,6 +1789,9 @@ static const struct net_device_ops ether_netdev_ops = { .ndo_change_mtu = ether_change_mtu, .ndo_select_queue = ether_select_queue, .ndo_set_features = ether_set_features, + .ndo_set_rx_mode = ether_set_rx_mode, + .ndo_vlan_rx_add_vid = ether_vlan_rx_add_vid, + .ndo_vlan_rx_kill_vid = ether_vlan_rx_kill_vid, }; /** @@ -2246,7 +2522,8 @@ static int ether_parse_phy_dt(struct ether_priv_data *pdata, * Algorithm: Reads queue priority form DT. Updates * data either by DT values or by default value. * - * Dependencies: None + * Dependencies: All queue priorities should be different + * from DT. * * Protection: None * @@ -2364,6 +2641,11 @@ static int ether_parse_dt(struct ether_priv_data *pdata) ETHER_QUEUE_PRIO_DEFAULT, ETHER_QUEUE_PRIO_MAX, osi_core->num_mtl_queues); + ether_parse_queue_prio(pdata, "nvidia,rx_queue_prio", + osi_core->rxq_prio, + ETHER_QUEUE_PRIO_INVALID, ETHER_QUEUE_PRIO_MAX, + osi_core->num_mtl_queues); + ret = ether_parse_phy_dt(pdata, np); if (ret < 0) { dev_err(dev, "failed to parse PHY DT\n"); @@ -2529,8 +2811,9 @@ static void ether_set_ndev_features(struct net_device *ndev, features |= NETIF_F_HW_VLAN_CTAG_TX; } - /* Rx VLAN tag detection enabled by default */ + /* Rx VLAN tag stripping/filtering enabled by default */ features |= NETIF_F_HW_VLAN_CTAG_RX; + features |= NETIF_F_HW_VLAN_CTAG_FILTER; /* Features available in HW */ ndev->hw_features = features; @@ -2670,6 +2953,36 @@ static int ether_therm_init(struct ether_priv_data *pdata) } #endif /* THERMAL_CAL */ +/** + * init_filter_values- static function to initialize filter reg + * count in private data structure + * + * @ether_priv_data: ethernet private data structure + * + * Algorithm: + * 1) update addr_reg_cnt based on HW feature + * + * Dependencies: MAC_HW_Feature1 register need to read and store the + * value of ADDR64. + * + * Protection: None. + * + * Return: None. + * + */ +static void init_filter_values(struct ether_priv_data *pdata) +{ + if (pdata->hw_feat.mac_addr64_sel == OSI_ENABLE) { + pdata->num_mac_addr_regs = ETHER_ADDR_REG_CNT_128; + } else if (pdata->hw_feat.mac_addr32_sel == OSI_ENABLE) { + pdata->num_mac_addr_regs = ETHER_ADDR_REG_CNT_64; + } else if (pdata->hw_feat.mac_addr16_sel == OSI_ENABLE) { + pdata->num_mac_addr_regs = ETHER_ADDR_REG_CNT_32; + } else { + pdata->num_mac_addr_regs = ETHER_ADDR_REG_CNT_1; + } +} + /** * ether_probe - Ethernet platform driver probe. * @pdev: platform device associated with platform driver. @@ -2680,6 +2993,8 @@ static int ether_therm_init(struct ether_priv_data *pdata) * 3) Parse MAC and PHY DT. * 4) Get all required clks/reset/IRQ's * 5) Register MDIO bus and network device. + * 6) initialize spinlock + * 7) Update filter value based on HW feature * * Dependencies: Device tree need to be updated with proper DT properties. * @@ -2824,6 +3139,10 @@ static int ether_probe(struct platform_device *pdev) goto err_netdev; } + spin_lock_init(&pdata->lock); + spin_lock_init(&pdata->ioctl_lock); + init_filter_values(pdata); + dev_info(&pdev->dev, "%s (HW ver: %02x) created with %u DMA channels\n", netdev_name(ndev), osi_core->mac_ver, num_dma_chans); diff --git a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h index 3732b388..97c9400c 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h +++ b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,20 @@ #define ETHER_IRQ_NAME_SZ 32 #define ETHER_QUEUE_PRIO_DEFAULT 0U #define ETHER_QUEUE_PRIO_MAX 7U +#define ETHER_QUEUE_PRIO_INVALID 0xFFU + +#define EQOS_CONFIG_FAIL -3 +#define EQOS_CONFIG_SUCCESS 0 + +#define ETHER_ADDR_REG_CNT_128 128 +#define ETHER_ADDR_REG_CNT_64 64 +#define ETHER_ADDR_REG_CNT_32 32 +#define ETHER_ADDR_REG_CNT_1 1 + +#define HW_HASH_TBL_SZ_3 3 +#define HW_HASH_TBL_SZ_2 2 +#define HW_HASH_TBL_SZ_1 1 +#define HW_HASH_TBL_SZ_0 0 /* Map max. 4KB buffer per Tx descriptor */ #define ETHER_MAX_DATA_LEN_PER_TXD_BUF BIT(12) @@ -146,6 +161,17 @@ struct ether_rx_napi { * is tripped. * @therm_state: Atomic variable to hold the current temperature zone * which has triggered. + * @lock: Spin lock for filter code + * @ioctl_lock: Spin lock for filter code ioctl path + * @max_hash_table_size: hash table size; 0, 64,128 or 256 + * @num_mac_addr_regs: max address register count, 2*mac_addr64_sel + * @last_mc_filter_index: Last Multicast address reg filter index, If 0, + * no MC address added + * @last_uc_filter_index: Last Unicast address reg filter index, If 0, no + * MC and UC address added. + * @l3_l4_filter: L3_l4 filter enabled 1: enabled + * @vlan_hash_filtering: vlan hash filter 1: hash, 0: perfect + * @l2_filtering_mode: l2 filter mode 1: hash 0: perfect */ struct ether_priv_data { struct osi_core_priv_data *osi_core; @@ -194,6 +220,17 @@ struct ether_priv_data { struct thermal_cooling_device *tcd; atomic_t therm_state; #endif /* THERMAL_CAL */ + + /* for filtering */ + spinlock_t lock; + /* spin lock for ioctl path */ + spinlock_t ioctl_lock; + int num_mac_addr_regs; + int last_mc_filter_index; + int last_uc_filter_index; + unsigned int l3_l4_filter; + unsigned int vlan_hash_filtering; + unsigned int l2_filtering_mode; }; void ether_set_ethtool_ops(struct net_device *ndev); diff --git a/drivers/net/ethernet/nvidia/nvethernet/ioctl.c b/drivers/net/ethernet/nvidia/nvethernet/ioctl.c index 9947c92d..312c3d9b 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/ioctl.c +++ b/drivers/net/ethernet/nvidia/nvethernet/ioctl.c @@ -162,6 +162,393 @@ static int ether_config_arp_offload(struct ether_priv_data *pdata, return ret; } +/** + * ether_config_l3_l4_filtering- This function is invoked by ioctl + * when user issues an ioctl command to enable/disable L3/L4 filtering. + * + * @dev: pointer to net device structure. + * @filter_flags: flag to indicate whether L3/L4 filtering to be + * enabled/disabled. + * + * Algorithm: + * 1) check if filter enalbed/disable already and return success. + * 2) OSI call to update register + * + * Dependencies: MAC and PHY need to be initialized. + * + * Protection: None. + * + * Return 0- sucessful, non-zero - error + * + */ +static int ether_config_l3_l4_filtering(struct net_device *dev, + unsigned int filter_flags) +{ + struct ether_priv_data *pdata = netdev_priv(dev); + struct osi_core_priv_data *osi_core = pdata->osi_core; + int ret = 0; + + if (filter_flags == pdata->l3_l4_filter) { + dev_err(pdata->dev, "L3/L4 filtering is already %d\n", + filter_flags); + return ret; + } + + ret = osi_config_l3_l4_filter_enable(osi_core, filter_flags); + if (ret == 0) { + spin_lock_bh(&pdata->ioctl_lock); + pdata->l3_l4_filter = filter_flags; + spin_unlock_bh(&pdata->ioctl_lock); + } + + return ret; +} + +/** + * ether_config_ip4_filters - this function is invoked by ioctl function + * when user issues an ioctl command to configure L3(IPv4) filtering. + * + * @dev: Pointer to net device structure. + * @ifdata: pointer to IOCTL specific structure. + * + * Algorithm: + * 1) Enable/disable IPv4 filtering. + * 2) Select source/destination address matching. + * 3) Select perfect/inverse matching. + * 4) Update the IPv4 address into MAC register. + * + * Dependencies: MAC and PHY need to be initialized. + * + * Protection: None. + * + * Return 0- sucessful, non-zero - error + */ +static int ether_config_ip4_filters(struct net_device *dev, + struct ether_ifr_data *ifdata) +{ + struct ether_priv_data *pdata = netdev_priv(dev); + struct osi_core_priv_data *osi_core = pdata->osi_core; + struct osi_l3_l4_filter *u_l3_filter = + (struct osi_l3_l4_filter *)ifdata->ptr; + struct osi_l3_l4_filter l_l3_filter; + int ret = -EINVAL; + + if (pdata->hw_feat.l3l4_filter_num == OSI_DISABLE) { + dev_err(pdata->dev, "ip4 filter is not supported\n"); + return ret; + } + + if (ifdata->ptr == NULL) { + dev_err(pdata->dev, "%s: Invalid data for priv ioctl %d\n", + __func__, ifdata->ifcmd); + return ret; + } + + if (copy_from_user(&l_l3_filter, u_l3_filter, + sizeof(struct osi_l3_l4_filter)) != 0U) { + dev_err(pdata->dev, "%s copy from user failed\n", __func__); + return -EFAULT; + } + + if (l_l3_filter.filter_no > (pdata->hw_feat.l3l4_filter_num - 1U)) { + dev_err(pdata->dev, "%d filter is not supported in the HW\n", + l_l3_filter.filter_no); + return ret; + } + + spin_lock_bh(&pdata->ioctl_lock); + if (pdata->l3_l4_filter == OSI_DISABLE) { + ret = osi_config_l3_l4_filter_enable(osi_core, 1); + if (ret == 0) { + pdata->l3_l4_filter = OSI_ENABLE; + } + } + spin_unlock_bh(&pdata->ioctl_lock); + + /* configure the L3 filters */ + ret = osi_config_l3_filters(osi_core, l_l3_filter.filter_no, + l_l3_filter.filter_enb_dis, + OSI_IP4_FILTER, + l_l3_filter.src_dst_addr_match, + l_l3_filter.perfect_inverse_match); + if (ret != 0) { + dev_err(pdata->dev, "osi_config_l3_filters failed\n"); + return ret; + } + + ret = osi_update_ip4_addr(osi_core, l_l3_filter.filter_no, + l_l3_filter.ip4_addr, + l_l3_filter.src_dst_addr_match); + + return ret; +} + +/** + * ether_config_ip6_filters- This function is invoked by ioctl when user + * issues an ioctl command to configure L3(IPv6) filtering. + * + * @dev: pointer to net device structure. + * @ifdata:pointer to IOCTL specific structure. + * + * Algorithm: + * 1) Enable/disable IPv6 filtering. + * 2) Select source/destination address matching. + * 3) Select perfect/inverse matching. + * 4) Update the IPv6 address into MAC register. + * + * Dependencies: MAC and PHY need to be initialized. + * + * Protection: None. + * Return 0- sucessful, non-zero - error + * + */ +static int ether_config_ip6_filters(struct net_device *dev, + struct ether_ifr_data *ifdata) +{ + struct ether_priv_data *pdata = netdev_priv(dev); + struct osi_core_priv_data *osi_core = pdata->osi_core; + struct osi_l3_l4_filter *u_l3_filter = + (struct osi_l3_l4_filter *)ifdata->ptr; + struct osi_l3_l4_filter l_l3_filter; + int ret = -EINVAL; + + if (pdata->hw_feat.l3l4_filter_num == OSI_DISABLE) { + dev_err(pdata->dev, "ip6 filter is not supported in the HW\n"); + return ret; + } + + if (ifdata->ptr == NULL) { + dev_err(pdata->dev, "%s: Invalid data for priv ioctl %d\n", + __func__, ifdata->ifcmd); + return ret; + } + + if (copy_from_user(&l_l3_filter, u_l3_filter, + sizeof(struct osi_l3_l4_filter)) != 0U) { + dev_err(pdata->dev, "%s copy from user failed\n", __func__); + return -EFAULT; + } + + if (l_l3_filter.filter_no > (pdata->hw_feat.l3l4_filter_num - 1U)) { + dev_err(pdata->dev, "%d filter is not supported in the HW\n", + l_l3_filter.filter_no); + return ret; + } + + spin_lock_bh(&pdata->ioctl_lock); + if (pdata->l3_l4_filter == OSI_DISABLE) { + ret = osi_config_l3_l4_filter_enable(osi_core, 1); + if (ret == 0) { + pdata->l3_l4_filter = OSI_ENABLE; + } + } + spin_unlock_bh(&pdata->ioctl_lock); + + /* configure the L3 filters */ + ret = osi_config_l3_filters(osi_core, l_l3_filter.filter_no, + l_l3_filter.filter_enb_dis, + OSI_IP6_FILTER, + l_l3_filter.src_dst_addr_match, + l_l3_filter.perfect_inverse_match); + if (ret != 0) { + dev_err(pdata->dev, "osi_config_l3_filters failed\n"); + return ret; + } + + return osi_update_ip6_addr(osi_core, l_l3_filter.filter_no, + l_l3_filter.ip6_addr); +} + +/** + * ether_config_tcp_udp_filters- This function is invoked by + * ioctl function when user issues an ioctl command to configure + * L4(TCP/UDP) filtering. + * + * @dev: pointer to net device structure. + * @ifdata: pointer to IOCTL specific structure. + * @tcp_udp: flag to indicate TCP/UDP filtering. + * + * Algorithm: + * 1) Enable/disable L4 filtering. + * 2) Select TCP/UDP filtering. + * 3) Select source/destination port matching. + * 4) select perfect/inverse matching. + * 5) Update the port number into MAC register. + * + * Dependencies: MAC and PHY need to be initialized. + * + * Protection: None. + * + * Return 0- sucessful, non-zero - error + * + */ +static int ether_config_tcp_udp_filters(struct net_device *dev, + struct ether_ifr_data *ifdata, + unsigned int tcp_udp) +{ + struct ether_priv_data *pdata = netdev_priv(dev); + struct osi_core_priv_data *osi_core = pdata->osi_core; + struct osi_l3_l4_filter *u_l4_filter = + (struct osi_l3_l4_filter *)ifdata->ptr; + struct osi_l3_l4_filter l_l4_filter; + int ret = -EINVAL; + + if (ifdata->ptr == NULL) { + dev_err(pdata->dev, "%s: Invalid data for priv ioctl %d\n", + __func__, ifdata->ifcmd); + return ret; + } + + if (pdata->hw_feat.l3l4_filter_num == OSI_DISABLE) { + dev_err(pdata->dev, + "L4 is not supported in the HW\n"); + return ret; + } + + if (copy_from_user(&l_l4_filter, u_l4_filter, + sizeof(struct osi_l3_l4_filter)) != 0U) { + dev_err(pdata->dev, "%s copy from user failed", __func__); + return -EFAULT; + } + + if (l_l4_filter.filter_no > (pdata->hw_feat.l3l4_filter_num - 1U)) { + dev_err(pdata->dev, "%d filter is not supported in the HW\n", + l_l4_filter.filter_no); + return ret; + } + + /* configure the L4 filters */ + ret = osi_config_l4_filters(osi_core, l_l4_filter.filter_no, + l_l4_filter.filter_enb_dis, + tcp_udp, + l_l4_filter.src_dst_addr_match, + l_l4_filter.perfect_inverse_match); + if (ret != 0) { + dev_err(pdata->dev, "osi_config_l4_filters failed\n"); + return ret; + } + + ret = osi_update_l4_port_no(osi_core, l_l4_filter.filter_no, + l_l4_filter.port_no, + l_l4_filter.src_dst_addr_match); + + return ret; +} + +/** + * ether_config_vlan_filter- This function is invoked by ioctl function + * when user issues an ioctl command to configure VALN filtering. + * + * @dev: pointer to net device structure. + * @ifdata: pointer to IOCTL specific structure. + * + * Algorithm: + * 1) enable/disable VLAN filtering. + * 2) select perfect/hash filtering. + * + * Dependencies: MAC and PHY need to be initialized. + * + * Protection: None. + * + * Return 0- sucessful, non-zero - error + * + */ +static int ether_config_vlan_filter(struct net_device *dev, + struct ether_ifr_data *ifdata) +{ + struct ether_priv_data *pdata = netdev_priv(dev); + struct osi_core_priv_data *osi_core = pdata->osi_core; + struct osi_vlan_filter *u_vlan_filter = + (struct osi_vlan_filter *)ifdata->ptr; + struct osi_vlan_filter l_vlan_filter; + int ret = -EINVAL; + + if (ifdata->ptr == NULL) { + dev_err(pdata->dev, "%s: Invalid data for priv ioctl %d\n", + __func__, ifdata->ifcmd); + return ret; + } + + if (copy_from_user(&l_vlan_filter, u_vlan_filter, + sizeof(struct osi_vlan_filter)) != 0U) { + dev_err(pdata->dev, "%s copy from user failed", __func__); + return -EFAULT; + } + + /*0 - perfect and 1 - hash filtering */ + if ((l_vlan_filter.perfect_hash == OSI_HASH_FILTER_MODE) && + (pdata->hw_feat.vlan_hash_en == OSI_DISABLE)) { + dev_err(pdata->dev, "VLAN HASH filtering is not supported\n"); + return ret; + } + /* configure the vlan filter FIXME: Current code supports VLAN + filtering for last VLAN tag/id added or default tag/vid 1. */ + ret = osi_config_vlan_filtering(osi_core, l_vlan_filter.filter_enb_dis, + l_vlan_filter.perfect_hash, + l_vlan_filter.perfect_inverse_match); + if (ret == 0) { + pdata->vlan_hash_filtering = l_vlan_filter.perfect_hash; + } + + return ret; +} + +/** + * ether_config_l2_da_filter- This function is invoked by ioctl function + * when user issues an ioctl command to configure L2 destination + * addressing filtering mode. + * + * @dev: Pointer to net device structure. + * @ifdata: Pointer to IOCTL specific structure. + * + * Algorithm: + * 1) Selects perfect/hash filtering. + * 2) Selects perfect/inverse matching. + * + * Dependencies: MAC and PHY need to be initialized. + * + * Protection: None. + * + * Return 0- sucessful, non-zero - error + */ +static int ether_config_l2_da_filter(struct net_device *dev, + struct ether_ifr_data *ifdata) +{ + struct ether_priv_data *pdata = netdev_priv(dev); + struct osi_core_priv_data *osi_core = pdata->osi_core; + struct osi_l2_da_filter *u_l2_da_filter = + (struct osi_l2_da_filter *)ifdata->ptr; + struct osi_l2_da_filter l_l2_da_filter; + int ret = -EINVAL; + + if (ifdata->ptr == NULL) { + dev_err(pdata->dev, "%s: Invalid data for priv ioctl %d\n", + __func__, ifdata->ifcmd); + return ret; + } + + if (copy_from_user(&l_l2_da_filter, u_l2_da_filter, + sizeof(struct osi_l2_da_filter)) != 0U) { + return -EFAULT; + } + if (l_l2_da_filter.perfect_hash == OSI_HASH_FILTER_MODE) { + dev_err(pdata->dev, + "select HASH FILTERING for L2 DA is not Supported in SW\n"); + return ret; + } else { + /* FIXME: Need to understand if filtering will work on addr0. + * Do we need to have pdata->num_mac_addr_regs > 1 check? + */ + pdata->l2_filtering_mode = OSI_PERFECT_FILTER_MODE; + } + + /* configure L2 DA perfect/inverse_matching */ + ret = osi_config_l2_da_perfect_inverse_match(osi_core, + l_l2_da_filter.perfect_inverse_match); + + return ret; +} + /** * ether_priv_ioctl - Handle private IOCTLs * @ndev: network device structure @@ -202,6 +589,43 @@ int ether_handle_priv_ioctl(struct net_device *ndev, case ETHER_CONFIG_ARP_OFFLOAD: ret = ether_config_arp_offload(pdata, &ifdata); break; + case EQOS_L3_L4_FILTER_CMD: + /* flags should be 0x0 or 0x1, discard any other */ + if (pdata->hw_feat.l3l4_filter_num > 0U && + ((ifdata.if_flags == OSI_ENABLE) || + (ifdata.if_flags == OSI_DISABLE))) { + ret = ether_config_l3_l4_filtering(ndev, + ifdata.if_flags); + if (ret == 0) { + ret = EQOS_CONFIG_SUCCESS; + } else { + ret = EQOS_CONFIG_FAIL; + } + } else { + dev_err(pdata->dev, "L3/L4 filters are not supported\n"); + ret = -EOPNOTSUPP; + } + break; + case EQOS_IPV4_FILTERING_CMD: + ret = ether_config_ip4_filters(ndev, &ifdata); + break; + case EQOS_IPV6_FILTERING_CMD: + ret = ether_config_ip6_filters(ndev, &ifdata); + break; + case EQOS_UDP_FILTERING_CMD: + ret = ether_config_tcp_udp_filters(ndev, &ifdata, + OSI_L4_FILTER_UDP); + break; + case EQOS_TCP_FILTERING_CMD: + ret = ether_config_tcp_udp_filters(ndev, &ifdata, + OSI_L4_FILTER_TCP); + break; + case EQOS_VLAN_FILTERING_CMD: + ret = ether_config_vlan_filter(ndev, &ifdata); + break; + case EQOS_L2_DA_FILTERING_CMD: + ret = ether_config_l2_da_filter(ndev, &ifdata); + break; default: break; } diff --git a/drivers/net/ethernet/nvidia/nvethernet/ioctl.h b/drivers/net/ethernet/nvidia/nvethernet/ioctl.h index a1b1443f..b1dc9aef 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/ioctl.h +++ b/drivers/net/ethernet/nvidia/nvethernet/ioctl.h @@ -20,12 +20,23 @@ #define NUM_BYTES_IN_IPADDR 4 #define MAX_IP_ADDR_BYTE 0xFF /* Remote wakeup filter */ -#define EQOS_RWK_FILTER_LENGTH 8 +#define EQOS_RWK_FILTER_LENGTH 8 /* private ioctl number*/ -#define ETHER_AVB_ALGORITHM 27 -#define ETHER_GET_AVB_ALGORITHM 46 +#define ETHER_AVB_ALGORITHM 27 +/* L3/L4 filter */ +#define EQOS_L3_L4_FILTER_CMD 29 +/* IPv4/6 and TCP/UDP filtering */ +#define EQOS_IPV4_FILTERING_CMD 30 +#define EQOS_IPV6_FILTERING_CMD 31 +#define EQOS_UDP_FILTERING_CMD 32 +#define EQOS_TCP_FILTERING_CMD 33 +/* VLAN filtering */ +#define EQOS_VLAN_FILTERING_CMD 34 +/* L2 DA filtering */ +#define EQOS_L2_DA_FILTERING_CMD 35 #define ETHER_CONFIG_ARP_OFFLOAD 36 +#define ETHER_GET_AVB_ALGORITHM 46 /** * struct ether_ifr_data - Private data of struct ifreq