diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.c b/drivers/net/ethernet/intel/ice/ice_adapter.c index 2dc3629d6d0f..536923b6ae97 100644 --- a/drivers/net/ethernet/intel/ice/ice_adapter.c +++ b/drivers/net/ethernet/intel/ice/ice_adapter.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "ice_adapter.h" #include "ice.h" @@ -54,11 +55,18 @@ static unsigned long ice_adapter_xa_index(struct pci_dev *pdev) static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev) { struct ice_adapter *adapter; + int err; adapter = kzalloc_obj(*adapter); if (!adapter) return NULL; + err = init_srcu_struct(&adapter->ports.srcu); + if (err) { + kfree(adapter); + return NULL; + } + adapter->index = ice_adapter_index(pdev); spin_lock_init(&adapter->ptp_gltsyn_time_lock); spin_lock_init(&adapter->txq_ctx_lock); @@ -66,18 +74,19 @@ static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev) mutex_init(&adapter->cpi_phy_lock[i]); refcount_set(&adapter->refcount, 1); - mutex_init(&adapter->ports.lock); - INIT_LIST_HEAD(&adapter->ports.ports); + spin_lock_init(&adapter->ports.lock); + INIT_LIST_HEAD(&adapter->ports.list); return adapter; } static void ice_adapter_free(struct ice_adapter *adapter) { - WARN_ON(!list_empty(&adapter->ports.ports)); + WARN_ON(!list_empty(&adapter->ports.list)); for (int i = 0; i < ARRAY_SIZE(adapter->cpi_phy_lock); i++) mutex_destroy(&adapter->cpi_phy_lock[i]); - mutex_destroy(&adapter->ports.lock); + + cleanup_srcu_struct(&adapter->ports.srcu); kfree(adapter); } diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.h b/drivers/net/ethernet/intel/ice/ice_adapter.h index 4f695f32da3d..c61bd645e460 100644 --- a/drivers/net/ethernet/intel/ice/ice_adapter.h +++ b/drivers/net/ethernet/intel/ice/ice_adapter.h @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -17,15 +18,19 @@ struct ice_pf; /** * struct ice_port_list - data used to store the list of adapter ports * - * This structure contains data used to maintain a list of adapter ports + * This structure contains data used to maintain a list of adapter ports. + * Writers modifying the list *must* acquire the lock, and use SRCU safe list + * operations. Readers should use srcu_read_lock() on the provided domain. * - * @ports: list of ports - * @lock: protect access to the ports list + * @list: list of ports + * @lock: protect write access to the list + * @srcu: Sleepable RCU domain for this adapter */ struct ice_port_list { - struct list_head ports; - /* To synchronize the ports list operations */ - struct mutex lock; + struct list_head list; + /* To synchronize write operations on the port list */ + spinlock_t lock; + struct srcu_struct srcu; }; /** diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index b781004f856d..e149a818dcd4 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2021, Intel Corporation. */ +#include +#include +#include #include "ice.h" #include "ice_lib.h" #include "ice_trace.h" @@ -676,20 +679,37 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx) pf->ptp.tx_hwtstamp_good += tstamp_good; } +static void ice_ptp_release_port_srcu(struct kref *ref) +{ + /* wake_up_var() requires full ordering but kref_put only provides + * release ordering. + */ + smp_mb__after_atomic(); + wake_up_var(ref); +} + static void ice_ptp_tx_tstamp_owner(struct ice_pf *pf) { + struct ice_port_list *ports = &pf->adapter->ports; struct ice_ptp_port *port; + int srcu_idx; - mutex_lock(&pf->adapter->ports.lock); - list_for_each_entry(port, &pf->adapter->ports.ports, list_node) { + srcu_idx = srcu_read_lock(&ports->srcu); + list_for_each_entry_srcu(port, &ports->list, list_node, + srcu_read_lock_held(&ports->srcu)) { struct ice_ptp_tx *tx = &port->tx; - if (!tx || !tx->init) + if (!tx->init) + continue; + + if (!kref_get_unless_zero(&port->ref)) continue; ice_ptp_process_tx_tstamp(tx); + + kref_put(&port->ref, ice_ptp_release_port_srcu); } - mutex_unlock(&pf->adapter->ports.lock); + srcu_read_unlock(&ports->srcu, srcu_idx); } /** @@ -809,10 +829,19 @@ ice_ptp_mark_tx_tracker_stale(struct ice_ptp_tx *tx) static void ice_ptp_flush_all_tx_tracker(struct ice_pf *pf) { + struct ice_port_list *ports = &pf->adapter->ports; struct ice_ptp_port *port; + int srcu_idx; - list_for_each_entry(port, &pf->adapter->ports.ports, list_node) + srcu_idx = srcu_read_lock(&ports->srcu); + list_for_each_entry_srcu(port, &ports->list, list_node, + srcu_read_lock_held(&ports->srcu)) { + if (!kref_get_unless_zero(&port->ref)) + continue; ice_ptp_flush_tx_tracker(ptp_port_to_pf(port), &port->tx); + kref_put(&port->ref, ice_ptp_release_port_srcu); + } + srcu_read_unlock(&ports->srcu, srcu_idx); } /** @@ -1427,16 +1456,22 @@ static void ice_ptp_reset_phy_timestamping(struct ice_pf *pf) */ static void ice_ptp_restart_all_phy(struct ice_pf *pf) { - struct list_head *entry; + struct ice_port_list *ports = &pf->adapter->ports; + struct ice_ptp_port *port; + int srcu_idx; - list_for_each(entry, &pf->adapter->ports.ports) { - struct ice_ptp_port *port = list_entry(entry, - struct ice_ptp_port, - list_node); + srcu_idx = srcu_read_lock(&ports->srcu); + list_for_each_entry_srcu(port, &ports->list, list_node, + srcu_read_lock_held(&ports->srcu)) { + if (!kref_get_unless_zero(&port->ref)) + continue; if (port->link_up) ice_ptp_port_phy_restart(port); + + kref_put(&port->ref, ice_ptp_release_port_srcu); } + srcu_read_unlock(&ports->srcu, srcu_idx); } /** @@ -2699,19 +2734,28 @@ static bool ice_port_has_timestamps(struct ice_ptp_tx *tx) static bool ice_any_port_has_timestamps(struct ice_pf *pf) { + struct ice_port_list *ports = &pf->adapter->ports; + bool have_tstamps = false; struct ice_ptp_port *port; + int srcu_idx; - scoped_guard(mutex, &pf->adapter->ports.lock) { - list_for_each_entry(port, &pf->adapter->ports.ports, - list_node) { - struct ice_ptp_tx *tx = &port->tx; + srcu_idx = srcu_read_lock(&ports->srcu); + list_for_each_entry_srcu(port, &ports->list, list_node, + srcu_read_lock_held(&ports->srcu)) { + if (!kref_get_unless_zero(&port->ref)) + continue; - if (ice_port_has_timestamps(tx)) - return true; - } + if (ice_port_has_timestamps(&port->tx)) + have_tstamps = true; + + kref_put(&port->ref, ice_ptp_release_port_srcu); + + if (have_tstamps) + break; } + srcu_read_unlock(&ports->srcu, srcu_idx); - return false; + return have_tstamps; } bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf) @@ -2970,14 +3014,18 @@ void ice_ptp_queue_work(struct ice_pf *pf) static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild, enum ice_reset_req reset_type) { - struct list_head *entry; + struct ice_port_list *ports = &pf->adapter->ports; + struct ice_ptp_port *port; + int srcu_idx; - list_for_each(entry, &pf->adapter->ports.ports) { - struct ice_ptp_port *port = list_entry(entry, - struct ice_ptp_port, - list_node); + srcu_idx = srcu_read_lock(&ports->srcu); + list_for_each_entry_srcu(port, &ports->list, list_node, + srcu_read_lock_held(&ports->srcu)) { struct ice_pf *peer_pf = ptp_port_to_pf(port); + if (!kref_get_unless_zero(&port->ref)) + continue; + if (!ice_is_primary(&peer_pf->hw)) { if (rebuild) { /* TODO: When implementing rebuild=true: @@ -2989,7 +3037,10 @@ static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild, ice_ptp_prepare_for_reset(peer_pf, reset_type); } } + + kref_put(&port->ref, ice_ptp_release_port_srcu); } + srcu_read_unlock(&ports->srcu, srcu_idx); } /** @@ -3169,11 +3220,11 @@ static int ice_ptp_setup_pf(struct ice_pf *pf) return -ENODEV; INIT_LIST_HEAD(&ptp->port.list_node); - mutex_lock(&pf->adapter->ports.lock); + kref_init(&ptp->port.ref); - list_add(&ptp->port.list_node, - &pf->adapter->ports.ports); - mutex_unlock(&pf->adapter->ports.lock); + spin_lock(&pf->adapter->ports.lock); + list_add_rcu(&ptp->port.list_node, &pf->adapter->ports.list); + spin_unlock(&pf->adapter->ports.lock); /* Seed the per-PHY Tx reference clock usage map for this port. * Only meaningful on E825 (other MAC types don't expose tx-clk @@ -3195,13 +3246,23 @@ static int ice_ptp_setup_pf(struct ice_pf *pf) static void ice_ptp_cleanup_pf(struct ice_pf *pf) { + struct ice_port_list *ports = &pf->adapter->ports; struct ice_ptp *ptp = &pf->ptp; + struct kref *ref; - if (pf->hw.mac_type != ICE_MAC_UNKNOWN) { - mutex_lock(&pf->adapter->ports.lock); - list_del(&ptp->port.list_node); - mutex_unlock(&pf->adapter->ports.lock); - } + if (pf->hw.mac_type == ICE_MAC_UNKNOWN) + return; + + spin_lock(&ports->lock); + list_del_rcu(&ptp->port.list_node); + spin_unlock(&ports->lock); + + ref = &ptp->port.ref; + kref_put(ref, ice_ptp_release_port_srcu); + + wait_var_event(ref, !kref_read(ref)); + + synchronize_srcu(&ports->srcu); } /** diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h index 0e40fef3b4e8..12a4ffa4f57c 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.h +++ b/drivers/net/ethernet/intel/ice/ice_ptp.h @@ -5,6 +5,8 @@ #define _ICE_PTP_H_ #include +#include +#include #include #include "ice_ptp_hw.h" @@ -138,6 +140,7 @@ struct ice_ptp_tx { * and determine when the port's PHY offset is valid. * * @list_node: list member structure + * @ref: reference counter for use with adapter ports list * @tx: Tx timestamp tracking for this port * @ov_work: delayed work task for tracking when PHY offset is valid * @ps_lock: mutex used to protect the overall PTP PHY start procedure @@ -149,6 +152,7 @@ struct ice_ptp_tx { */ struct ice_ptp_port { struct list_head list_node; + struct kref ref; struct ice_ptp_tx tx; struct kthread_delayed_work ov_work; struct mutex ps_lock; /* protects overall PTP PHY start procedure */