Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net/ncsi: Reset channel state in ncsi_start_dev()

When the NCSI driver is stopped with ncsi_stop_dev() the channel
monitors are stopped and the state set to "inactive". However the
channels are still configured and active from the perspective of the
network controller. We should suspend each active channel but in the
context of ncsi_stop_dev() the transmit queue has been or is about to be
stopped so we won't have time to do so.

Instead when ncsi_start_dev() is called if the NCSI topology has already
been probed then call ncsi_reset_dev() to suspend any channels that were
previously active. This resets the network controller to a known state,
provides an up to date view of channel link state, and makes sure that
mode flags such as NCSI_MODE_TX_ENABLE are properly reset.

In addition to ncsi_start_dev() use ncsi_reset_dev() in ncsi-netlink.c
to update the channel configuration more cleanly.

Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Samuel Mendoza-Jonas and committed by
David S. Miller
2878a2cf 0b970e1b

+113 -12
+2
net/ncsi/internal.h
··· 287 287 #define NCSI_DEV_PROBED 1 /* Finalized NCSI topology */ 288 288 #define NCSI_DEV_HWA 2 /* Enabled HW arbitration */ 289 289 #define NCSI_DEV_RESHUFFLE 4 290 + #define NCSI_DEV_RESET 8 /* Reset state of NC */ 290 291 unsigned int gma_flag; /* OEM GMA flag */ 291 292 spinlock_t lock; /* Protect the NCSI device */ 292 293 #if IS_ENABLED(CONFIG_IPV6) ··· 343 342 list_for_each_entry_rcu(nc, &np->channels, node) 344 343 345 344 /* Resources */ 345 + int ncsi_reset_dev(struct ncsi_dev *nd); 346 346 void ncsi_start_channel_monitor(struct ncsi_channel *nc); 347 347 void ncsi_stop_channel_monitor(struct ncsi_channel *nc); 348 348 struct ncsi_channel *ncsi_find_channel(struct ncsi_package *np,
+105 -6
net/ncsi/ncsi-manage.c
··· 550 550 spin_lock_irqsave(&nc->lock, flags); 551 551 nc->state = NCSI_CHANNEL_INACTIVE; 552 552 spin_unlock_irqrestore(&nc->lock, flags); 553 - ncsi_process_next_channel(ndp); 554 - 553 + if (ndp->flags & NCSI_DEV_RESET) 554 + ncsi_reset_dev(nd); 555 + else 556 + ncsi_process_next_channel(ndp); 555 557 break; 556 558 default: 557 559 netdev_warn(nd->dev, "Wrong NCSI state 0x%x in suspend\n", ··· 900 898 netdev_dbg(ndp->ndev.dev, "NCSI: channel %u config done\n", 901 899 nc->id); 902 900 spin_lock_irqsave(&nc->lock, flags); 901 + nc->state = NCSI_CHANNEL_ACTIVE; 902 + 903 + if (ndp->flags & NCSI_DEV_RESET) { 904 + /* A reset event happened during config, start it now */ 905 + nc->reconfigure_needed = false; 906 + spin_unlock_irqrestore(&nc->lock, flags); 907 + ncsi_reset_dev(nd); 908 + break; 909 + } 910 + 903 911 if (nc->reconfigure_needed) { 904 912 /* This channel's configuration has been updated 905 913 * part-way during the config state - start the ··· 928 916 break; 929 917 } 930 918 931 - nc->state = NCSI_CHANNEL_ACTIVE; 932 919 if (nc->modes[NCSI_MODE_LINK].data[2] & 0x1) { 933 920 hot_nc = nc; 934 921 } else { ··· 1565 1554 return 0; 1566 1555 } 1567 1556 1568 - return ncsi_choose_active_channel(ndp); 1557 + return ncsi_reset_dev(nd); 1569 1558 } 1570 1559 EXPORT_SYMBOL_GPL(ncsi_start_dev); 1571 1560 ··· 1578 1567 int old_state; 1579 1568 unsigned long flags; 1580 1569 1581 - /* Stop the channel monitor and reset channel's state */ 1570 + /* Stop the channel monitor on any active channels. Don't reset the 1571 + * channel state so we know which were active when ncsi_start_dev() 1572 + * is next called. 1573 + */ 1582 1574 NCSI_FOR_EACH_PACKAGE(ndp, np) { 1583 1575 NCSI_FOR_EACH_CHANNEL(np, nc) { 1584 1576 ncsi_stop_channel_monitor(nc); ··· 1589 1575 spin_lock_irqsave(&nc->lock, flags); 1590 1576 chained = !list_empty(&nc->link); 1591 1577 old_state = nc->state; 1592 - nc->state = NCSI_CHANNEL_INACTIVE; 1593 1578 spin_unlock_irqrestore(&nc->lock, flags); 1594 1579 1595 1580 WARN_ON_ONCE(chained || ··· 1600 1587 ncsi_report_link(ndp, true); 1601 1588 } 1602 1589 EXPORT_SYMBOL_GPL(ncsi_stop_dev); 1590 + 1591 + int ncsi_reset_dev(struct ncsi_dev *nd) 1592 + { 1593 + struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); 1594 + struct ncsi_channel *nc, *active, *tmp; 1595 + struct ncsi_package *np; 1596 + unsigned long flags; 1597 + 1598 + spin_lock_irqsave(&ndp->lock, flags); 1599 + 1600 + if (!(ndp->flags & NCSI_DEV_RESET)) { 1601 + /* Haven't been called yet, check states */ 1602 + switch (nd->state & ncsi_dev_state_major) { 1603 + case ncsi_dev_state_registered: 1604 + case ncsi_dev_state_probe: 1605 + /* Not even probed yet - do nothing */ 1606 + spin_unlock_irqrestore(&ndp->lock, flags); 1607 + return 0; 1608 + case ncsi_dev_state_suspend: 1609 + case ncsi_dev_state_config: 1610 + /* Wait for the channel to finish its suspend/config 1611 + * operation; once it finishes it will check for 1612 + * NCSI_DEV_RESET and reset the state. 1613 + */ 1614 + ndp->flags |= NCSI_DEV_RESET; 1615 + spin_unlock_irqrestore(&ndp->lock, flags); 1616 + return 0; 1617 + } 1618 + } else { 1619 + switch (nd->state) { 1620 + case ncsi_dev_state_suspend_done: 1621 + case ncsi_dev_state_config_done: 1622 + case ncsi_dev_state_functional: 1623 + /* Ok */ 1624 + break; 1625 + default: 1626 + /* Current reset operation happening */ 1627 + spin_unlock_irqrestore(&ndp->lock, flags); 1628 + return 0; 1629 + } 1630 + } 1631 + 1632 + if (!list_empty(&ndp->channel_queue)) { 1633 + /* Clear any channel queue we may have interrupted */ 1634 + list_for_each_entry_safe(nc, tmp, &ndp->channel_queue, link) 1635 + list_del_init(&nc->link); 1636 + } 1637 + spin_unlock_irqrestore(&ndp->lock, flags); 1638 + 1639 + active = NULL; 1640 + NCSI_FOR_EACH_PACKAGE(ndp, np) { 1641 + NCSI_FOR_EACH_CHANNEL(np, nc) { 1642 + spin_lock_irqsave(&nc->lock, flags); 1643 + 1644 + if (nc->state == NCSI_CHANNEL_ACTIVE) { 1645 + active = nc; 1646 + nc->state = NCSI_CHANNEL_INVISIBLE; 1647 + spin_unlock_irqrestore(&nc->lock, flags); 1648 + ncsi_stop_channel_monitor(nc); 1649 + break; 1650 + } 1651 + 1652 + spin_unlock_irqrestore(&nc->lock, flags); 1653 + } 1654 + if (active) 1655 + break; 1656 + } 1657 + 1658 + if (!active) { 1659 + /* Done */ 1660 + spin_lock_irqsave(&ndp->lock, flags); 1661 + ndp->flags &= ~NCSI_DEV_RESET; 1662 + spin_unlock_irqrestore(&ndp->lock, flags); 1663 + return ncsi_choose_active_channel(ndp); 1664 + } 1665 + 1666 + spin_lock_irqsave(&ndp->lock, flags); 1667 + ndp->flags |= NCSI_DEV_RESET; 1668 + ndp->active_channel = active; 1669 + ndp->active_package = active->package; 1670 + spin_unlock_irqrestore(&ndp->lock, flags); 1671 + 1672 + nd->state = ncsi_dev_state_suspend; 1673 + schedule_work(&ndp->work); 1674 + return 0; 1675 + } 1603 1676 1604 1677 void ncsi_unregister_dev(struct ncsi_dev *nd) 1605 1678 {
+6 -6
net/ncsi/ncsi-netlink.c
··· 330 330 package_id, channel_id, 331 331 channel_id == NCSI_RESERVED_CHANNEL ? " (any)" : ""); 332 332 333 - /* Bounce the NCSI channel to set changes */ 334 - ncsi_stop_dev(&ndp->ndev); 335 - ncsi_start_dev(&ndp->ndev); 333 + /* Update channel configuration */ 334 + if (!(ndp->flags & NCSI_DEV_RESET)) 335 + ncsi_reset_dev(&ndp->ndev); 336 336 337 337 return 0; 338 338 } ··· 360 360 spin_unlock_irqrestore(&ndp->lock, flags); 361 361 netdev_info(ndp->ndev.dev, "NCSI: Cleared preferred package/channel\n"); 362 362 363 - /* Bounce the NCSI channel to set changes */ 364 - ncsi_stop_dev(&ndp->ndev); 365 - ncsi_start_dev(&ndp->ndev); 363 + /* Update channel configuration */ 364 + if (!(ndp->flags & NCSI_DEV_RESET)) 365 + ncsi_reset_dev(&ndp->ndev); 366 366 367 367 return 0; 368 368 }