@@ -987,6 +987,65 @@ static void i40e_disable_vf_mappings(struct i40e_vf *vf)
i40e_flush(hw);
}
+/**
+ * i40e_add_vmmac_to_list
+ * @vf: pointer to the VF info
+ * @macaddr: pointer to the MAC address
+ *
+ * add MAC address into the MAC list for VM
+ **/
+static int i40e_add_vmmac_to_list(struct i40e_vf *vf, const u8 *macaddr)
+{
+ struct i40e_vm_mac *mac_elem;
+
+ mac_elem = kzalloc(sizeof(*mac_elem), GFP_ATOMIC);
+ if (!mac_elem)
+ return -ENOMEM;
+ ether_addr_copy(mac_elem->macaddr, macaddr);
+ INIT_LIST_HEAD(&mac_elem->list);
+ list_add(&mac_elem->list, &vf->vm_mac_list);
+ return 0;
+}
+
+/**
+ * i40e_del_vmmac_from_list
+ * @vf: pointer to the VF info
+ * @macaddr: pointer to the MAC address
+ *
+ * delete MAC address from the MAC list for VM
+ **/
+static void i40e_del_vmmac_from_list(struct i40e_vf *vf, const u8 *macaddr)
+{
+ struct i40e_vm_mac *entry, *tmp;
+
+ list_for_each_entry_safe(entry, tmp, &vf->vm_mac_list, list) {
+ if (ether_addr_equal(macaddr, entry->macaddr)) {
+ list_del(&entry->list);
+ kfree(entry);
+ break;
+ }
+ }
+}
+
+/**
+ * i40e_free_vmmac_list
+ * @vf: pointer to the VF info
+ *
+ * remove whole list of MAC addresses for VM
+ **/
+static void i40e_free_vmmac_list(struct i40e_vf *vf)
+{
+ struct i40e_vm_mac *entry, *tmp;
+
+ if (list_empty(&vf->vm_mac_list))
+ return;
+
+ list_for_each_entry_safe(entry, tmp, &vf->vm_mac_list, list) {
+ list_del(&entry->list);
+ kfree(entry);
+ }
+}
+
/**
* i40e_free_vf_res
* @vf: pointer to the VF info
@@ -1062,6 +1121,9 @@ static void i40e_free_vf_res(struct i40e_vf *vf)
wr32(hw, reg_idx, reg);
i40e_flush(hw);
}
+
+ i40e_free_vmmac_list(vf);
+
/* reset some of the state variables keeping track of the resources */
vf->num_queue_pairs = 0;
clear_bit(I40E_VF_STATE_MC_PROMISC, &vf->vf_states);
@@ -2915,27 +2977,111 @@ static inline int i40e_check_vf_permission(struct i40e_vf *vf,
}
/**
- * i40e_vc_add_mac_addr_msg
+ * i40e_vc_ether_addr_type - get type of virtchnl_ether_addr
+ * @vc_ether_addr: used to extract the type
+ **/
+static u8
+i40e_vc_ether_addr_type(struct virtchnl_ether_addr *vc_ether_addr)
+{
+ return vc_ether_addr->type & VIRTCHNL_ETHER_ADDR_TYPE_MASK;
+}
+
+/**
+ * i40e_is_vc_addr_legacy
+ * @vc_ether_addr: VIRTCHNL structure that contains MAC and type
+ *
+ * check if the MAC address is from an older VF
+ **/
+static bool
+i40e_is_vc_addr_legacy(struct virtchnl_ether_addr *vc_ether_addr)
+{
+ return i40e_vc_ether_addr_type(vc_ether_addr) ==
+ VIRTCHNL_ETHER_ADDR_LEGACY;
+}
+
+/**
+ * i40e_is_vc_addr_primary
+ * @vc_ether_addr: VIRTCHNL structure that contains MAC and type
+ *
+ * check if the MAC address is the VF's primary MAC
+ * This function should only be called when the MAC address in
+ * virtchnl_ether_addr is a valid unicast MAC
+ **/
+static bool
+i40e_is_vc_addr_primary(struct virtchnl_ether_addr *vc_ether_addr)
+{
+ return i40e_vc_ether_addr_type(vc_ether_addr) ==
+ VIRTCHNL_ETHER_ADDR_PRIMARY;
+}
+
+#define I40E_LEGACY_VF_MAC_CHANGE_EXPIRE_TIME msecs_to_jiffies(3000)
+
+/**
+ * i40e_is_legacy_umac_expired
+ * @time_last_added_umac: time since the last delete of VFs default MAC
+ *
+ * check if last added legacy unicast MAC expired
+ **/
+static bool
+i40e_is_legacy_umac_expired(unsigned long time_last_added_umac)
+{
+ return time_is_before_jiffies(time_last_added_umac +
+ I40E_LEGACY_VF_MAC_CHANGE_EXPIRE_TIME);
+}
+
+/**
+ * i40e_update_vf_mac_addr
+ * @vf: VF to update
+ * @vc_ether_addr: structure from VIRTCHNL with MAC to add
+ *
+ * update the VF's cached hardware MAC if allowed
+ **/
+static void
+i40e_update_vf_mac_addr(struct i40e_vf *vf,
+ struct virtchnl_ether_addr *vc_ether_addr)
+{
+ u8 *mac_addr = vc_ether_addr->addr;
+
+ if (!is_valid_ether_addr(mac_addr))
+ return;
+
+ /* If request to add MAC filter is a primary request
+ * update its default MAC address with the requested one.
+ * if it is a legacy request then check if current default is empty
+ * if so update the default MAC
+ * otherwise save it in case it is followed by a delete request
+ * meaning VF wants to change its default MAC which will be updated
+ * in the delete path
+ */
+ if (i40e_is_vc_addr_primary(vc_ether_addr)) {
+ ether_addr_copy(vf->default_lan_addr.addr, mac_addr);
+ } else if (i40e_is_vc_addr_legacy(vc_ether_addr)) {
+ if (is_zero_ether_addr(vf->default_lan_addr.addr)) {
+ ether_addr_copy(vf->default_lan_addr.addr, mac_addr);
+ } else {
+ ether_addr_copy(vf->legacy_last_added_umac.addr,
+ mac_addr);
+ vf->legacy_last_added_umac.time_modified = jiffies;
+ }
+ }
+}
+
+/**
+ * i40e_add_vf_mac_filters
* @vf: pointer to the VF info
- * @msg: pointer to the msg buffer
+ * @is_quiet: set true for printing msg without opcode info, false otherwise
+ * @al: pointer to the address list of MACs to add
*
* add guest mac address filter
**/
-static int i40e_vc_add_mac_addr_msg(struct i40e_vf *vf, u8 *msg)
+static int i40e_add_vf_mac_filters(struct i40e_vf *vf, bool *is_quiet,
+ struct virtchnl_ether_addr_list *al)
{
- struct virtchnl_ether_addr_list *al =
- (struct virtchnl_ether_addr_list *)msg;
struct i40e_pf *pf = vf->pf;
struct i40e_vsi *vsi = NULL;
int ret = 0;
int i;
- if (!i40e_sync_vf_state(vf, I40E_VF_STATE_ACTIVE) ||
- !i40e_vc_isvalid_vsi_id(vf, al->vsi_id)) {
- ret = I40E_ERR_PARAM;
- goto error_param;
- }
-
vsi = pf->vsi[vf->lan_vsi_idx];
/* Lock once, because all function inside for loop accesses VSI's
@@ -2956,7 +3102,6 @@ static int i40e_vc_add_mac_addr_msg(struct i40e_vf *vf, u8 *msg)
f = i40e_find_mac(vsi, al->list[i].addr);
if (!f) {
f = i40e_add_mac_filter(vsi, al->list[i].addr);
-
if (!f) {
dev_err(&pf->pdev->dev,
"Unable to add MAC filter %pM for VF %d\n",
@@ -2965,11 +3110,15 @@ static int i40e_vc_add_mac_addr_msg(struct i40e_vf *vf, u8 *msg)
spin_unlock_bh(&vsi->mac_filter_hash_lock);
goto error_param;
}
- if (is_valid_ether_addr(al->list[i].addr) &&
- is_zero_ether_addr(vf->default_lan_addr.addr))
- ether_addr_copy(vf->default_lan_addr.addr,
- al->list[i].addr);
+
+ ret = i40e_add_vmmac_to_list(vf, al->list[i].addr);
+ if (ret) {
+ spin_unlock_bh(&vsi->mac_filter_hash_lock);
+ goto error_param;
+ }
}
+
+ i40e_update_vf_mac_addr(vf, &al->list[i]);
}
spin_unlock_bh(&vsi->mac_filter_hash_lock);
@@ -2978,29 +3127,23 @@ static int i40e_vc_add_mac_addr_msg(struct i40e_vf *vf, u8 *msg)
if (ret)
dev_err(&pf->pdev->dev, "Unable to program VF %d MAC filters, error %d\n",
vf->vf_id, ret);
-
error_param:
- /* send the response to the VF */
- return i40e_vc_send_msg_to_vf(vf, VIRTCHNL_OP_ADD_ETH_ADDR,
- ret, NULL, 0);
+ return ret;
}
/**
- * i40e_vc_del_mac_addr_msg
+ * i40e_vc_add_mac_addr_msg
* @vf: pointer to the VF info
* @msg: pointer to the msg buffer
*
- * remove guest mac address filter
+ * add guest mac address filter
**/
-static int i40e_vc_del_mac_addr_msg(struct i40e_vf *vf, u8 *msg)
+static int i40e_vc_add_mac_addr_msg(struct i40e_vf *vf, u8 *msg)
{
struct virtchnl_ether_addr_list *al =
(struct virtchnl_ether_addr_list *)msg;
- bool was_unimac_deleted = false;
- struct i40e_pf *pf = vf->pf;
- struct i40e_vsi *vsi = NULL;
+ bool is_quiet = false;
int ret = 0;
- int i;
if (!i40e_sync_vf_state(vf, I40E_VF_STATE_ACTIVE) ||
!i40e_vc_isvalid_vsi_id(vf, al->vsi_id)) {
@@ -3008,52 +3151,127 @@ static int i40e_vc_del_mac_addr_msg(struct i40e_vf *vf, u8 *msg)
goto error_param;
}
+ ret = i40e_add_vf_mac_filters(vf, &is_quiet, al);
+
+error_param:
+ /* send the response to the VF */
+ return i40e_vc_send_msg_to_vf(vf, VIRTCHNL_OP_ADD_ETH_ADDR,
+ ret, NULL, 0);
+}
+
+/**
+ * i40e_vf_clear_default_mac_addr
+ * @vf: pointer to the VF info
+ * @is_legacy_unimac: is request to delete a legacy request
+ *
+ * clear VFs default MAC address
+ **/
+static void i40e_vf_clear_default_mac_addr(struct i40e_vf *vf,
+ bool is_legacy_unimac)
+{
+ eth_zero_addr(vf->default_lan_addr.addr);
+
+ if (is_legacy_unimac) {
+ unsigned long time_added =
+ vf->legacy_last_added_umac.time_modified;
+
+ if (!i40e_is_legacy_umac_expired(time_added))
+ ether_addr_copy(vf->default_lan_addr.addr,
+ vf->legacy_last_added_umac.addr);
+ }
+}
+
+/**
+ * i40e_del_vf_mac_filters
+ * @vf: pointer to the VF info
+ * @al: pointer to the address list of MACs to delete
+ *
+ * remove guest mac address filters
+ **/
+static int i40e_del_vf_mac_filters(struct i40e_vf *vf,
+ struct virtchnl_ether_addr_list *al)
+{
+ bool was_unimac_deleted = false;
+ bool is_legacy_unimac = false;
+ struct i40e_pf *pf = vf->pf;
+ struct i40e_vsi *vsi = NULL;
+ int ret = 0;
+ int i;
+
+ vsi = pf->vsi[vf->lan_vsi_idx];
+
+ spin_lock_bh(&vsi->mac_filter_hash_lock);
+ /* delete addresses from the list */
for (i = 0; i < al->num_elements; i++) {
+ if (ether_addr_equal(al->list[i].addr,
+ vf->default_lan_addr.addr)) {
+ if (!(vf->trusted || !vf->pf_set_mac)) {
+ dev_err(&pf->pdev->dev,
+ "VF attempting to override administratively set MAC address\n");
+ ret = -EPERM;
+ spin_unlock_bh(&vsi->mac_filter_hash_lock);
+ goto error_param;
+ } else {
+ was_unimac_deleted = true;
+ is_legacy_unimac =
+ i40e_is_vc_addr_legacy(&al->list[i]);
+ }
+ }
+
if (is_broadcast_ether_addr(al->list[i].addr) ||
is_zero_ether_addr(al->list[i].addr)) {
dev_err(&pf->pdev->dev, "Invalid MAC addr %pM for VF %d\n",
al->list[i].addr, vf->vf_id);
- ret = I40E_ERR_INVALID_MAC_ADDR;
+ ret = -EINVAL;
+ spin_unlock_bh(&vsi->mac_filter_hash_lock);
goto error_param;
}
- if (ether_addr_equal(al->list[i].addr, vf->default_lan_addr.addr))
- was_unimac_deleted = true;
- }
- vsi = pf->vsi[vf->lan_vsi_idx];
- spin_lock_bh(&vsi->mac_filter_hash_lock);
- /* delete addresses from the list */
- for (i = 0; i < al->num_elements; i++)
if (i40e_del_mac_filter(vsi, al->list[i].addr)) {
- ret = I40E_ERR_INVALID_MAC_ADDR;
+ dev_err(&pf->pdev->dev, "Could not delete MAC addr %pM for VF %d\n",
+ al->list[i].addr, vf->vf_id);
+ ret = -EINVAL;
spin_unlock_bh(&vsi->mac_filter_hash_lock);
goto error_param;
}
+ i40e_del_vmmac_from_list(vf, al->list[i].addr);
+ }
spin_unlock_bh(&vsi->mac_filter_hash_lock);
+ if (was_unimac_deleted)
+ i40e_vf_clear_default_mac_addr(vf, is_legacy_unimac);
+
/* program the updated filter list */
ret = i40e_sync_vsi_filters(vsi);
if (ret)
dev_err(&pf->pdev->dev, "Unable to program VF %d MAC filters, error %d\n",
vf->vf_id, ret);
+error_param:
+ return ret;
+}
- if (vf->trusted && was_unimac_deleted) {
- struct i40e_mac_filter *f;
- struct hlist_node *h;
- u8 *macaddr = NULL;
- int bkt;
+/**
+ * i40e_vc_del_mac_addr_msg
+ * @vf: pointer to the VF info
+ * @msg: pointer to the msg buffer
+ *
+ * remove guest mac address filter
+ **/
+static int i40e_vc_del_mac_addr_msg(struct i40e_vf *vf, u8 *msg)
+{
+ struct virtchnl_ether_addr_list *al =
+ (struct virtchnl_ether_addr_list *)msg;
+ int ret = 0;
- /* set last unicast mac address as default */
- spin_lock_bh(&vsi->mac_filter_hash_lock);
- hash_for_each_safe(vsi->mac_filter_hash, bkt, h, f, hlist) {
- if (is_valid_ether_addr(f->macaddr))
- macaddr = f->macaddr;
- }
- if (macaddr)
- ether_addr_copy(vf->default_lan_addr.addr, macaddr);
- spin_unlock_bh(&vsi->mac_filter_hash_lock);
+ if (!i40e_sync_vf_state(vf, I40E_VF_STATE_ACTIVE) ||
+ !i40e_vc_isvalid_vsi_id(vf, al->vsi_id)) {
+ ret = -EINVAL;
+ goto error_param;
}
+
+ ret = i40e_del_vf_mac_filters(vf, al);
+
error_param:
/* send the response to the VF */
return i40e_vc_send_resp_to_vf(vf, VIRTCHNL_OP_DEL_ETH_ADDR, ret);
@@ -62,6 +62,17 @@ struct i40evf_channel {
u64 max_tx_rate; /* bandwidth rate allocation for VSIs */
};
+struct i40e_time_mac {
+ unsigned long time_modified;
+ u8 addr[ETH_ALEN];
+};
+
+/* used for MAC list 'vm_mac_list' to recognize MACs added by VM */
+struct i40e_vm_mac {
+ struct list_head list;
+ u8 macaddr[ETH_ALEN];
+};
+
/* VF information structure */
struct i40e_vf {
struct i40e_pf *pf;
@@ -77,6 +88,10 @@ struct i40e_vf {
u16 stag;
struct virtchnl_ether_addr default_lan_addr;
+
+ /* keeps last added MAC address */
+ struct i40e_time_mac legacy_last_added_umac;
+
u16 port_vlan_id;
bool pf_set_mac; /* The VMM admin set the VF MAC address */
bool trusted;
@@ -100,6 +115,9 @@ struct i40e_vf {
bool spoofchk;
u16 num_vlan;
+ /* MAC list created by VM */
+ struct list_head vm_mac_list;
+
/* ADq related variables */
bool adq_enabled; /* flag to enable adq */
u8 num_tc;