This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ibmveth: Add per-queue RX and TX statistics collection
This adds per-queue RX and TX counter arrays that are allocated at probe
and freed at remove, updates them from the poll/IRQ/xmit paths, and
exposes the values through ethtool -S plus a new ndo_get_stats64
implementation. It also adds the hcall_* counters to the ethtool string
table and reworks the probe error paths and ibmveth_remove() teardown
order.
A few questions below.
> diff --git a/drivers/net/ethernet/ibm/ibmveth.c
> b/drivers/net/ethernet/ibm/ibmveth.c
> index 36f0926711e60..3202b657c9b8f 100644
> --- a/drivers/net/ethernet/ibm/ibmveth.c
> +++ b/drivers/net/ethernet/ibm/ibmveth.c
> @@ -98,7 +98,17 @@ static struct ibmveth_stat ibmveth_stats[] = {
> { "fw_enabled_ipv6_csum", IBMVETH_STAT_OFF(fw_ipv6_csum_support) },
> { "tx_large_packets", IBMVETH_STAT_OFF(tx_large_packets) },
> { "rx_large_packets", IBMVETH_STAT_OFF(rx_large_packets) },
> - { "fw_enabled_large_send", IBMVETH_STAT_OFF(fw_large_send_support) }
> + { "fw_enabled_large_send", IBMVETH_STAT_OFF(fw_large_send_support) },
> + { "hcall_reg_lan_queue", IBMVETH_STAT_OFF(hcall_stats.reg_lan_queue) },
> + { "hcall_reg_lan", IBMVETH_STAT_OFF(hcall_stats.reg_lan) },
> + { "hcall_add_bufs_queue",
> + IBMVETH_STAT_OFF(hcall_stats.add_bufs_queue) },
> + { "hcall_add_bufs", IBMVETH_STAT_OFF(hcall_stats.add_bufs) },
> + { "hcall_add_buf", IBMVETH_STAT_OFF(hcall_stats.add_buf) },
> + { "hcall_free_lan_queue",
> + IBMVETH_STAT_OFF(hcall_stats.free_lan_queue) },
> + { "hcall_free_lan", IBMVETH_STAT_OFF(hcall_stats.free_lan) },
> + { "hcall_send_lan", IBMVETH_STAT_OFF(hcall_stats.send_lan) },
> };
[Severity: Low]
Can these hcall_* values lose increments once they become user visible?
The fields in adapter->hcall_stats are plain u64 bumped with an
unserialized read-modify-write. ibmveth_replenish_task() takes only the
queue-local lock:
spin_lock_irqsave(&rxq->replenish_lock, flags);
so two NAPI instances replenishing different RX queues both reach
adapter->hcall_stats.add_bufs_queue++;
with no shared serialization, and separate TX queues both reach
adapter->hcall_stats.send_lan++;
in ibmveth_send(). Two CPUs can read N and both store N+1.
Would per-queue counters summed on read (the same approach used for the
new rx_qstats/tx_qstats), or atomic64_t, be preferable here?
[ ... ]
> @@ -1011,7 +1075,23 @@ static void ibmveth_update_rx_no_buffer(struct
> ibmveth_adapter *adapter,
> p = adapter->buffer_list_addr[queue_index] + 4096 - 8;
> drops = be64_to_cpup(p);
>
> - adapter->rx_no_buffer = drops;
> + /*
> + * PHYP's buffer-list page counter is absolute for that page. A new
> + * page (reopen / queue reuse after -L) starts near zero; fold the
> + * previous absolute into retired so adapter-level sums stay
> + * monotonic.
> + */
> + if (adapter->rx_qstats) {
> + u64 *slot = &adapter->rx_qstats[queue_index].no_buffer_drops;
> +
> + if (drops < *slot)
> + adapter->rx_no_buffer_retired += *slot;
> + *slot = drops;
> + } else {
> + if (drops < adapter->rx_no_buffer)
> + adapter->rx_no_buffer_retired += adapter->rx_no_buffer;
> + adapter->rx_no_buffer = drops;
> + }
> }
[Severity: Low]
Does the no_buffer_drops slot lose the final PHYP value when a queue is
retired by a channel shrink?
This makes rx_qstats[i].no_buffer_drops a mirror of the page-absolute
counter, and folding into rx_no_buffer_retired only happens when a later
read observes a decrease. ibmveth_update_rx_no_buffer() has only two call
sites, the replenish path and ibmveth_close(), and it early-returns for
queue_index >= ibmveth_get_num_rx_queues(adapter)
Once the incremental resize patch later in this series lands, scale-down
does:
ibmveth_publish_num_rx_queues(adapter, new_count);
...
for (i = new_count; i < old_count; i++)
ibmveth_destroy_subordinate_rx_queue(adapter, i);
which reaches ibmveth_free_single_rx_queue() and dma_unmap_single() plus
free_page() on buffer_list_addr[i]. The live count is already lowered, so
a late harvest is rejected by the guard above, and everything PHYP
recorded in that page since the last replenish read is dropped from both
the per-queue slot and the adapter sum.
Would harvesting the absolute before lowering the published queue count
and freeing the page fix this?
> @@ -2239,22 +2319,158 @@ static int ibmveth_set_features(struct net_device
> *dev,
[ ... ]
> static void ibmveth_get_strings(struct net_device *dev, u32 stringset, u8
> *data)
> {
> + struct ibmveth_adapter *adapter = netdev_priv(dev);
> + u8 *p = data;
> int i;
>
> if (stringset != ETH_SS_STATS)
> return;
>
> - for (i = 0; i < ARRAY_SIZE(ibmveth_stats); i++, data += ETH_GSTRING_LEN)
> - memcpy(data, ibmveth_stats[i].name, ETH_GSTRING_LEN);
> + for (i = 0; i < ARRAY_SIZE(ibmveth_stats); i++) {
> + memcpy(p, ibmveth_stats[i].name, ETH_GSTRING_LEN);
> + p += ETH_GSTRING_LEN;
> + }
> +
> + for (i = 0; i < ibmveth_get_num_rx_queues(adapter); i++) {
> + ethtool_sprintf(&p, "rx%d_packets", i);
> + ethtool_sprintf(&p, "rx%d_bytes", i);
> + ethtool_sprintf(&p, "rx%d_interrupts", i);
> + ethtool_sprintf(&p, "rx%d_polls", i);
> + ethtool_sprintf(&p, "rx%d_large_packets", i);
> + ethtool_sprintf(&p, "rx%d_invalid_buffers", i);
> + ethtool_sprintf(&p, "rx%d_no_buffer_drops", i);
> + }
> +
> + for (i = 0; i < dev->real_num_tx_queues; i++) {
> + ethtool_sprintf(&p, "tx%d_packets", i);
> + ethtool_sprintf(&p, "tx%d_bytes", i);
> + ethtool_sprintf(&p, "tx%d_large_packets", i);
> + ethtool_sprintf(&p, "tx%d_dropped_packets", i);
> + ethtool_sprintf(&p, "tx%d_send_failures", i);
> + ethtool_sprintf(&p, "tx%d_checksum_offload", i);
> + }
[Severity: Medium]
Should the per-queue packet, byte and drop counters go through the
standard per-queue statistics interface instead of private ethtool
strings?
rx%d_packets, rx%d_bytes, tx%d_packets, tx%d_bytes and
tx%d_dropped_packets map directly onto existing fields:
include/net/netdev_queues.h
struct netdev_stat_ops {
void (*get_queue_stats_rx)(struct net_device *dev, int idx,
struct netdev_queue_stats_rx *stats);
...
The driver adds only .ndo_get_stats64 (device-wide) and never sets
netdev->stat_ops, so the newly collected per-queue values are reachable
only through the private ethtool blob, which cannot be removed once
shipped. The genuinely driver-specific counters (interrupts, polls,
invalid_buffers, no_buffer_drops, send_failures, checksum_offload) look
fine in ethtool -S.
Could the packets/bytes/dropped set be exposed via netdev_stat_ops
qstats instead?
> +
> + for (i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++) {
> + ethtool_sprintf(&p, "pool%d_size", i);
> + ethtool_sprintf(&p, "pool%d_active", i);
> + ethtool_sprintf(&p, "pool%d_available", i);
> + }
> }
>
> static int ibmveth_get_sset_count(struct net_device *dev, int sset)
> {
> + struct ibmveth_adapter *adapter = netdev_priv(dev);
> +
> switch (sset) {
> case ETH_SS_STATS:
> - return ARRAY_SIZE(ibmveth_stats);
> + return ARRAY_SIZE(ibmveth_stats) +
> + ibmveth_get_num_rx_queues(adapter) *
> + IBMVETH_NUM_RX_QSTATS +
> + dev->real_num_tx_queues * IBMVETH_NUM_TX_QSTATS +
> + IBMVETH_NUM_BUFF_POOLS * 3;
> default:
> return -EOPNOTSUPP;
> }
> @@ -2263,11 +2479,44 @@ static int ibmveth_get_sset_count(struct net_device
> *dev, int sset)
> static void ibmveth_get_ethtool_stats(struct net_device *dev,
> struct ethtool_stats *stats, u64 *data)
> {
[ ... ]
> + for (j = 0; j < IBMVETH_NUM_BUFF_POOLS; j++) {
> + data[i++] = adapter->rx_buff_pool[0][j].size;
> + data[i++] = adapter->rx_buff_pool[0][j].active;
> + data[i++] = atomic_read(&adapter->rx_buff_pool[0][j].available);
> + }
> }
[Severity: Medium]
This is a pre-existing issue rather than something this patch creates for
the lifetime part below, but the commit message describes only per-queue
RX/TX statistics and the hcall_* strings, while the patch also carries two
other changes.
First, three new ethtool -S entries per buffer pool are added:
ethtool_sprintf(&p, "pool%d_size", i);
ethtool_sprintf(&p, "pool%d_active", i);
ethtool_sprintf(&p, "pool%d_available", i);
and counted as IBMVETH_NUM_BUFF_POOLS * 3 in ibmveth_get_sset_count().
The values are read only from adapter->rx_buff_pool[0][j], but
rx_buff_pool is declared as
struct ibmveth_buff_pool
rx_buff_pool[IBMVETH_MAX_RX_QUEUES][IBMVETH_NUM_BUFF_POOLS];
so in multi-queue mode queues 1..N-1 are not represented and the string
names carry no queue index. pool size and active are also already
available through the per-pool sysfs attributes (veth_size_attr,
veth_num_attr, veth_active_attr). Should these strings be dropped, or at
least given a queue index and mentioned in the commit message?
Second, ibmveth_remove() reorders unregister_netdev() ahead of
cancel_work_sync(), and the new ibmveth_probe_cleanup() clears vio
drvdata before free_netdev(). The in-code comments describe both as
fixing existing problems (a worker running after free_netdev(), and CMO
get_desired_dma() seeing a freed netdev on rebind), neither of which the
commit message body mentions and neither of which has a Fixes: tag.
Could these be split into their own patches?
[ ... ]
> @@ -3107,6 +3410,7 @@ static const struct net_device_ops ibmveth_netdev_ops =
> {
> .ndo_validate_addr = eth_validate_addr,
> .ndo_set_mac_address = ibmveth_set_mac_addr,
> .ndo_features_check = ibmveth_features_check,
> + .ndo_get_stats64 = ibmveth_get_stats64,
> #ifdef CONFIG_NET_POLL_CONTROLLER
> .ndo_poll_controller = ibmveth_poll_controller,
> #endif
> @@ -3121,6 +3425,23 @@ static void ibmveth_put_pool_kobjs(struct
> ibmveth_adapter *adapter,
> kobject_put(&adapter->rx_buff_pool[0][i].kobj);
> }
>
> +static void ibmveth_probe_cleanup(struct ibmveth_adapter *adapter,
> + int pools_ready)
> +{
> + struct net_device *netdev = adapter->netdev;
> +
> + cancel_work_sync(&adapter->work);
> + ibmveth_put_pool_kobjs(adapter, pools_ready);
> +
> + ibmveth_free_tx_qstats(adapter);
> + ibmveth_free_rx_qstats(adapter);
> + /* Probe failure never reaches ibmveth_remove(); clear before free so
> + * CMO get_desired_dma() cannot see a freed netdev on rebind.
> + */
> + dev_set_drvdata(&adapter->vdev->dev, NULL);
> + free_netdev(netdev);
> +}
> +
[ ... ]
> @@ -3332,12 +3651,20 @@ static void ibmveth_remove(struct vio_dev *dev)
> struct ibmveth_adapter *adapter = netdev_priv(netdev);
> int i;
>
> - cancel_work_sync(&adapter->work);
> -
> for (i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++)
> kobject_put(&adapter->rx_buff_pool[0][i].kobj);
>
> + /*
> + * Unregister first so NAPI/xmit cannot re-arm reset work after we
> + * cancel it. cancel_work_sync() before unregister left a window
> + * where poll could schedule_work() and the worker ran after
> + * free_netdev().
> + */
> unregister_netdev(netdev);
> + cancel_work_sync(&adapter->work);
> +
> + ibmveth_free_tx_qstats(adapter);
> + ibmveth_free_rx_qstats(adapter);
>
> free_netdev(netdev);
> dev_set_drvdata(&dev->dev, NULL);
[Severity: High]
With cancel_work_sync() now running after unregister_netdev(), can the
reset worker re-open an already unregistered netdev?
adapter->work is queued from several error paths, for example
ibmveth_replenish_task() on an invalid free_map entry or on MQ H_FUNCTION
after LPM, and from ibmveth_poll_skip_bad_correlator():
schedule_work(&adapter->work);
The handler has no registration or running check:
drivers/net/ethernet/ibm/ibmveth.c:ibmveth_reset() {
rtnl_lock();
dev_close(adapter->netdev);
dev_open(adapter->netdev, NULL);
rtnl_unlock();
}
A worker already queued (or already blocked in rtnl_lock() while
unregister_netdev() holds rtnl) proceeds once unregistration finishes.
dev_close() is then a no-op, and the re-open is not rejected:
net/core/dev.c:netif_open() {
if (dev->flags & IFF_UP)
return 0;
ret = __dev_open(dev, extack);
}
net/core/dev.c:__dev_open() {
ASSERT_RTNL();
dev_addr_check(dev);
if (!netif_device_present(dev)) {
...
if (!ret && ops->ndo_open)
ret = ops->ndo_open(dev);
}
IFF_UP was cleared by unregister and __LINK_STATE_PRESENT is never
cleared (the driver does not call netif_device_detach()), so
ibmveth_open() runs again on the unregistered device: it re-requests the
RX IRQs with dev_id = &adapter->napi[i], re-registers the logical LAN
with PHYP, and re-arms DMA into freshly allocated buffer lists.
cancel_work_sync() then returns and remove() continues into
ibmveth_free_tx_qstats() / ibmveth_free_rx_qstats() and free_netdev(),
so the installed IRQ handler and the hypervisor are left pointing at
freed memory. The next RX interrupt would execute
adapter->rx_qstats[qindex].interrupts++;
in ibmveth_interrupt() against a freed adapter and a freed qstat array.
Would a netif_running() or reg_state check inside ibmveth_reset(), or a
"removing" flag set before unregister_netdev(), close this?