From 7dd4c829bac2916be98a3e34b41daaba7f42b4c4 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 20 Jul 2026 22:58:46 +0900 Subject: [PATCH 1/5] idpf: disable DIM work before freeing q_vectors idpf never drains the Tx/Rx DIM works before freeing the memory they live in. tx_dim and rx_dim are embedded in struct idpf_q_vector, they are queued from the NAPI poll via net_dim(), and idpf_vport_intr_rel() ends with kfree(rsrc->q_vectors). Nothing in the driver cancels them. idpf_tx_dim_work() and idpf_rx_dim_work() then run on freed memory: idpf_vport_intr_write_itr() writes the ITR register through q_vector->intr_reg.tx_itr / rx_itr, void __iomem pointers loaded out of the freed q_vector. No configuration is needed to get there -- IDPF_ITR_IS_DYNAMIC() is defined as (itr_mode) and idpf_vport_alloc() initialises both modes to IDPF_ITR_DYNAMIC. Draining after idpf_vport_intr_napi_dis_all() is not enough on its own. idpf_net_dim() is called from inside the "if (napi_complete_done(napi, work_done))" branch of the poll, and napi_complete_done() has already cleared NAPIF_STATE_SCHED by then. napi_disable_locked() waits only while (val & (NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC)), so napi_disable() can return while the poll tail is still queueing the work, and a plain cancel_work_sync() would be re-armed behind the drain. Use disable_work_sync(): schedule_work() on a work with a non-zero disable count is dropped by clear_pending_if_disabled() before __queue_work() is reached. Move idpf_init_dim() to idpf_vport_intr_alloc() so the works are initialised on every path that can reach the drain -- the three "goto intr_deinit" sites between idpf_vport_intr_init() and idpf_vport_intr_ena() get there without the enable side having run. Nothing re-enables them: rsrc->q_vectors is freed on every exit from idpf_vport_open() and on every idpf_vport_stop(), so the count dies with the object. It is a race, not a deterministic failure -- net_dim() only schedules once DIM_NEVENTS events have accumulated and the profile index changes. A KASAN ifup/ifdown loop under load is the way to see it. Fixes: c2d548cad150 ("idpf: add TX splitq napi poll support") Fixes: 3a8845af66ed ("idpf: add RX splitq napi poll support") Cc: # see patch description, needs adjustments for <= 6.9 Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Tested-by: Samuel Salin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 24b91be25676..9ba9c2952d78 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -4145,6 +4145,26 @@ static void idpf_vport_intr_ena_irq_all(struct idpf_vport *vport, writel(rsrc->noirq_dyn_ctl_ena, rsrc->noirq_dyn_ctl); } +/** + * idpf_vport_intr_dis_dim_all - Disable DIM work for all q_vectors + * @rsrc: pointer to queue and vector resources + * + * The DIM works are embedded in the q_vector array that + * idpf_vport_intr_rel() frees, and the poll arms them after + * napi_complete_done() has already cleared NAPI_STATE_SCHED. Disable + * rather than just cancel, so that a poll tail still running past + * napi_disable() cannot queue them again behind the drain. + */ +static void idpf_vport_intr_dis_dim_all(struct idpf_q_vec_rsrc *rsrc) +{ + for (u16 v_idx = 0; v_idx < rsrc->num_q_vectors; v_idx++) { + struct idpf_q_vector *q_vector = &rsrc->q_vectors[v_idx]; + + disable_work_sync(&q_vector->tx_dim.work); + disable_work_sync(&q_vector->rx_dim.work); + } +} + /** * idpf_vport_intr_deinit - Release all vector associations for the vport * @vport: main vport structure @@ -4155,6 +4175,7 @@ void idpf_vport_intr_deinit(struct idpf_vport *vport, { idpf_vport_intr_dis_irq_all(rsrc); idpf_vport_intr_napi_dis_all(rsrc); + idpf_vport_intr_dis_dim_all(rsrc); idpf_vport_intr_napi_del_all(rsrc); idpf_vport_intr_rel_irq(vport, rsrc); } @@ -4235,7 +4256,6 @@ static void idpf_vport_intr_napi_ena_all(struct idpf_q_vec_rsrc *rsrc) for (u16 q_idx = 0; q_idx < rsrc->num_q_vectors; q_idx++) { struct idpf_q_vector *q_vector = &rsrc->q_vectors[q_idx]; - idpf_init_dim(q_vector); napi_enable(&q_vector->napi); } } @@ -4578,6 +4598,8 @@ int idpf_vport_intr_alloc(struct idpf_vport *vport, q_coal = &user_config->q_coalesce[v_idx]; q_vector->vport = vport; + idpf_init_dim(q_vector); + q_vector->tx_itr_value = q_coal->tx_coalesce_usecs; q_vector->tx_intr_mode = q_coal->tx_intr_mode; q_vector->tx_itr_idx = VIRTCHNL2_ITR_IDX_1; From 650f197d8ea6ebbc9ce1c9fb0358258b65e74291 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 20 Jul 2026 23:35:10 +0900 Subject: [PATCH 2/5] idpf: disable PTM on probe failure and on remove idpf_probe() enables PCIe Precision Time Measurement with pci_enable_ptm(), which takes a reference on the device and on every PTM-capable device up the path to the PTM Root. Neither the probe error path nor idpf_remove() drops that reference, so the PTM enable counts of this device and of its upstream path stay elevated with no bound driver, and the device's PTM control bits remain set. pcim_enable_device() only arranges for pci_disable_device() and does not undo the PTM enable. Add the matching pci_disable_ptm() to the common unwind path. pci_enable_ptm() failure is not fatal here, so guard the call with pcie_ptm_enabled(): pci_disable_ptm() decrements dev->ptm_enable_cnt unconditionally and then recurses upstream, so calling it after a failed enable would drive this device's count negative and wrongly decrement parents shared with other endpoints. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 8d5e12c5921c ("idpf: add initial PTP support") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin [TN moved call due to commit 6b284aa2ddf3 ("idpf: refactor idpf to use libie_pci APIs")] Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_main.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/ethernet/intel/idpf/idpf_main.c b/drivers/net/ethernet/intel/idpf/idpf_main.c index 9840580fbe51..129bccaa6baa 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_main.c +++ b/drivers/net/ethernet/intel/idpf/idpf_main.c @@ -106,6 +106,11 @@ static int idpf_dev_init(struct idpf_adapter *adapter, */ static void idpf_decfg_device(struct idpf_adapter *adapter) { + struct pci_dev *pdev = adapter->pdev; + + if (pcie_ptm_enabled(pdev)) + pci_disable_ptm(pdev); + libie_pci_unmap_all_mmio_regions(&adapter->ctlq_ctx.mmio_info); } From cc6d60ef92278a31ffc2e94966a0921b9646af18 Mon Sep 17 00:00:00 2001 From: Joshua Hay Date: Mon, 27 Jul 2026 16:08:48 -0700 Subject: [PATCH 3/5] idpf: account for VLAN header when parsing RSC packet header While parsing the header of a Receive Side Coalesced (RSC) packet, check if a VLAN tag is present and adjust the header parsing accordingly. Otherwise, Rx TCP traffic is completely broken for any VLAN interface whose underlying interface has RSC (rx-gro-hw) enabled. We only need to worry about one VLAN header since Rx packets with multiple VLAN headers are not candidates for RSC. Fixes: 3a8845af66edb ("idpf: add RX splitq napi poll support") Signed-off-by: Joshua Hay Reviewed-by: Emil Tantilov Reviewed-by: Aleksandr Loktionov Tested-by: Samuel Salin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/idpf/idpf_txrx.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c index 9ba9c2952d78..4311ffa30bb1 100644 --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c @@ -3299,6 +3299,7 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, struct libeth_rx_pt decoded) { u16 rsc_segments, rsc_seg_len; + u16 l3_start = 0; bool ipv4, ipv6; int len; @@ -3321,7 +3322,10 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, NAPI_GRO_CB(skb)->count = rsc_segments; skb_shinfo(skb)->gso_size = rsc_seg_len; - skb_reset_network_header(skb); + if (unlikely(eth_type_vlan(skb->protocol))) + l3_start = VLAN_HLEN; + + skb_set_network_header(skb, l3_start); if (ipv4) { struct iphdr *ipv4h = ip_hdr(skb); @@ -3329,7 +3333,7 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4; /* Reset and set transport header offset in skb */ - skb_set_transport_header(skb, sizeof(struct iphdr)); + skb_set_transport_header(skb, l3_start + sizeof(struct iphdr)); len = skb->len - skb_transport_offset(skb); /* Compute the TCP pseudo header checksum*/ @@ -3339,7 +3343,7 @@ static int idpf_rx_rsc(struct idpf_rx_queue *rxq, struct sk_buff *skb, struct ipv6hdr *ipv6h = ipv6_hdr(skb); skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6; - skb_set_transport_header(skb, sizeof(struct ipv6hdr)); + skb_set_transport_header(skb, l3_start + sizeof(struct ipv6hdr)); len = skb->len - skb_transport_offset(skb); tcp_hdr(skb)->check = ~tcp_v6_check(len, &ipv6h->saddr, &ipv6h->daddr, 0); From 53432c4c3e869076350aef319534431af8ba99c1 Mon Sep 17 00:00:00 2001 From: Jacob Keller Date: Mon, 6 Jul 2026 16:31:17 -0700 Subject: [PATCH 4/5] ice: add missing xa_destroy for sched_node_ids Commit 16dfa49406bc ("ice: Introduce new parameters in ice_sched_node") added a sched_node_ids xarray to the port info structure, but never called xa_destroy on it. Since xarrays can allocate internal memory, this can result in a memory leak even if every element in the xarray has been removed. The xarray is currently embedded in the port_info structure. This appears to have been done because its use is within functions that take the port_info as a primary argument. However, this complicates managing the lifecycle of the field. The port_info structure is allocated in ice_init_hw() using devm, and it is not released until the devm cleanup when the driver is unloaded. The ice_init_hw() function is called in many places, including devlink reload, and possibly during DDP load after updating the Tx scheduler layout. Adding a call of xa_destroy to the ice_deinit_hw() causes Sashiko to raise multiple concerns due to potential ordering issues and possible ways that port_info could be a dangling reference. To handle this, move the sched_node_ids out of port_info and into the hw structure. All users of the array already have a pointer to hw anyways, and there is only one sched_node_ids per adapter. While here, remove the overly verbose comment explaining the nature of the sched_node_ids xarray. Add the missing xa_destroy to the cleanup path and to ice_deinit_hw(), ensuring that we properly release the xarray memory. This was caught by Sashiko during development of unrelated code. Fixes: 16dfa49406bc ("ice: Introduce new parameters in ice_sched_node") Signed-off-by: Jacob Keller Reviewed-by: Aleksandr Loktionov Tested-by: Rinitha S (A Contingent worker at Intel) Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_common.c | 9 ++++++--- drivers/net/ethernet/intel/ice/ice_sched.c | 4 ++-- drivers/net/ethernet/intel/ice/ice_type.h | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_common.c b/drivers/net/ethernet/intel/ice/ice_common.c index ef1ce106f81b..04633103e3e6 100644 --- a/drivers/net/ethernet/intel/ice/ice_common.c +++ b/drivers/net/ethernet/intel/ice/ice_common.c @@ -1051,14 +1051,13 @@ int ice_init_hw(struct ice_hw *hw) hw->evb_veb = true; - /* init xarray for identifying scheduling nodes uniquely */ - xa_init_flags(&hw->port_info->sched_node_ids, XA_FLAGS_ALLOC); + xa_init_flags(&hw->sched_node_ids, XA_FLAGS_ALLOC); /* Query the allocated resources for Tx scheduler */ status = ice_sched_query_res_alloc(hw); if (status) { ice_debug(hw, ICE_DBG_SCHED, "Failed to get scheduler allocated resources\n"); - goto err_unroll_alloc; + goto err_unroll_xarray; } ice_sched_get_psm_clk_freq(hw); @@ -1146,6 +1145,8 @@ int ice_init_hw(struct ice_hw *hw) ice_cleanup_fltr_mgmt_struct(hw); err_unroll_sched: ice_sched_cleanup_all(hw); +err_unroll_xarray: + xa_destroy(&hw->sched_node_ids); err_unroll_alloc: devm_kfree(ice_hw_to_dev(hw), hw->port_info); err_unroll_cqinit: @@ -1186,6 +1187,8 @@ void ice_deinit_hw(struct ice_hw *hw) /* Clear VSI contexts if not already cleared */ ice_clear_all_vsi_ctx(hw); + + xa_destroy(&hw->sched_node_ids); } /** diff --git a/drivers/net/ethernet/intel/ice/ice_sched.c b/drivers/net/ethernet/intel/ice/ice_sched.c index fff0c1afdb41..ffa18d86729a 100644 --- a/drivers/net/ethernet/intel/ice/ice_sched.c +++ b/drivers/net/ethernet/intel/ice/ice_sched.c @@ -371,7 +371,7 @@ void ice_free_sched_node(struct ice_port_info *pi, struct ice_sched_node *node) devm_kfree(ice_hw_to_dev(hw), node->children); kfree(node->name); - xa_erase(&pi->sched_node_ids, node->id); + xa_erase(&hw->sched_node_ids, node->id); devm_kfree(ice_hw_to_dev(hw), node); } @@ -977,7 +977,7 @@ ice_sched_add_elems(struct ice_port_info *pi, struct ice_sched_node *tc_node, if (!new_node->name) return -ENOMEM; - status = xa_alloc(&pi->sched_node_ids, &new_node->id, NULL, XA_LIMIT(0, UINT_MAX), + status = xa_alloc(&hw->sched_node_ids, &new_node->id, NULL, XA_LIMIT(0, UINT_MAX), GFP_KERNEL); if (status) { ice_debug(hw, ICE_DBG_SCHED, "xa_alloc failed for sched node status =%d\n", diff --git a/drivers/net/ethernet/intel/ice/ice_type.h b/drivers/net/ethernet/intel/ice/ice_type.h index d9a5c1aae7c2..cf147a212707 100644 --- a/drivers/net/ethernet/intel/ice/ice_type.h +++ b/drivers/net/ethernet/intel/ice/ice_type.h @@ -765,7 +765,6 @@ struct ice_port_info { /* List contain profile ID(s) and other params per layer */ struct list_head rl_prof_list[ICE_AQC_TOPO_MAX_LEVEL_NUM]; struct ice_qos_cfg qos_cfg; - struct xarray sched_node_ids; u8 is_vf:1; u8 is_custom_tx_enabled:1; }; @@ -930,6 +929,7 @@ struct ice_hw { u8 sw_entry_point_layer; u16 max_children[ICE_AQC_TOPO_MAX_LEVEL_NUM]; struct list_head agg_list; /* lists all aggregator */ + struct xarray sched_node_ids; struct ice_vsi_ctx *vsi_ctx[ICE_MAX_VSI]; u8 evb_veb; /* true for VEB, false for VEPA */ From b8bf9bfda5f62e11444e483c2b4aaff90c5cfc6b Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 20 Aug 2026 19:02:01 -0700 Subject: [PATCH 5/5] eth: ice: don't dereference pointers from TP_printk() After forwarding net-next during the v7.3 merge window we started seeing: TRACE EVENT ERROR: Event ice_tx_dim_work has double dereference in TP_printk: REC->q_vector->tx.tx_ring->q_index WARNING: kernel/trace/trace_events.c:420 at test_double_dereference.cold+0x39/0x4b this is due to extra checks added in tracing subsystem in commit b5cc230af5e5 ("tracing: Warn when an event dereferences a pointer in TP_printk()"). Printing happens long after the event was recorded, by which point the pointers may be invalid (the ring or the dim instance). Copy the eight scalars into the event instead. Fixes: 3089cf6d3caa ("ice: add tracepoints") Signed-off-by: Jakub Kicinski Tested-by: Alexander Nowlin Signed-off-by: Tony Nguyen --- drivers/net/ethernet/intel/ice/ice_trace.h | 64 ++++++++++++++-------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/drivers/net/ethernet/intel/ice/ice_trace.h b/drivers/net/ethernet/intel/ice/ice_trace.h index 4f35ef8d6b29..7568c917cdbe 100644 --- a/drivers/net/ethernet/intel/ice/ice_trace.h +++ b/drivers/net/ethernet/intel/ice/ice_trace.h @@ -63,23 +63,33 @@ DECLARE_EVENT_CLASS(ice_rx_dim_template, TP_PROTO(struct ice_q_vector *q_vector, struct dim *dim), TP_ARGS(q_vector, dim), - TP_STRUCT__entry(__field(struct ice_q_vector *, q_vector) - __field(struct dim *, dim) + TP_STRUCT__entry(__field(u16, q_index) + __field(u8, state) + __field(u8, profile_ix) + __field(u8, tune_state) + __field(u8, steps_right) + __field(u8, steps_left) + __field(u8, tired) __string(devname, q_vector->rx.rx_ring->netdev->name)), - TP_fast_assign(__entry->q_vector = q_vector; - __entry->dim = dim; + TP_fast_assign(__entry->q_index = q_vector->rx.rx_ring->q_index; + __entry->state = dim->state; + __entry->profile_ix = dim->profile_ix; + __entry->tune_state = dim->tune_state; + __entry->steps_right = dim->steps_right; + __entry->steps_left = dim->steps_left; + __entry->tired = dim->tired; __assign_str(devname);), TP_printk("netdev: %s Rx-Q: %d dim-state: %d dim-profile: %d dim-tune: %d dim-st-right: %d dim-st-left: %d dim-tired: %d", __get_str(devname), - __entry->q_vector->rx.rx_ring->q_index, - __entry->dim->state, - __entry->dim->profile_ix, - __entry->dim->tune_state, - __entry->dim->steps_right, - __entry->dim->steps_left, - __entry->dim->tired) + __entry->q_index, + __entry->state, + __entry->profile_ix, + __entry->tune_state, + __entry->steps_right, + __entry->steps_left, + __entry->tired) ); DEFINE_EVENT(ice_rx_dim_template, ice_rx_dim_work, @@ -90,23 +100,33 @@ DEFINE_EVENT(ice_rx_dim_template, ice_rx_dim_work, DECLARE_EVENT_CLASS(ice_tx_dim_template, TP_PROTO(struct ice_q_vector *q_vector, struct dim *dim), TP_ARGS(q_vector, dim), - TP_STRUCT__entry(__field(struct ice_q_vector *, q_vector) - __field(struct dim *, dim) + TP_STRUCT__entry(__field(u16, q_index) + __field(u8, state) + __field(u8, profile_ix) + __field(u8, tune_state) + __field(u8, steps_right) + __field(u8, steps_left) + __field(u8, tired) __string(devname, q_vector->tx.tx_ring->netdev->name)), - TP_fast_assign(__entry->q_vector = q_vector; - __entry->dim = dim; + TP_fast_assign(__entry->q_index = q_vector->tx.tx_ring->q_index; + __entry->state = dim->state; + __entry->profile_ix = dim->profile_ix; + __entry->tune_state = dim->tune_state; + __entry->steps_right = dim->steps_right; + __entry->steps_left = dim->steps_left; + __entry->tired = dim->tired; __assign_str(devname);), TP_printk("netdev: %s Tx-Q: %d dim-state: %d dim-profile: %d dim-tune: %d dim-st-right: %d dim-st-left: %d dim-tired: %d", __get_str(devname), - __entry->q_vector->tx.tx_ring->q_index, - __entry->dim->state, - __entry->dim->profile_ix, - __entry->dim->tune_state, - __entry->dim->steps_right, - __entry->dim->steps_left, - __entry->dim->tired) + __entry->q_index, + __entry->state, + __entry->profile_ix, + __entry->tune_state, + __entry->steps_right, + __entry->steps_left, + __entry->tired) ); DEFINE_EVENT(ice_tx_dim_template, ice_tx_dim_work,