// SPDX-License-Identifier: GPL-2.0-or-later /**************************************************************************/ /* */ /* IBM System i and System p Virtual NIC Device Driver */ /* Copyright (C) 2014 IBM Corp. */ /* Santiago Leon (santi_leon@yahoo.com) */ /* Thomas Falcon (tlfalcon@linux.vnet.ibm.com) */ /* John Allen (jallen@linux.vnet.ibm.com) */ /* */ /* */ /* This module contains the implementation of a virtual ethernet device */ /* for use with IBM i/p Series LPAR Linux. It utilizes the logical LAN */ /* option of the RS/6000 Platform Architecture to interface with virtual */ /* ethernet NICs that are presented to the partition by the hypervisor. */ /* */ /* Messages are passed between the VNIC driver and the VNIC server using */ /* Command/Response Queues (CRQs) and sub CRQs (sCRQs). CRQs are used to */ /* issue and receive commands that initiate communication with the server */ /* on driver initialization. Sub CRQs (sCRQs) are similar to CRQs, but */ /* are used by the driver to notify the server that a packet is */ /* ready for transmission or that a buffer has been added to receive a */ /* packet. Subsequently, sCRQs are used by the server to notify the */ /* driver that a packet transmission has been completed or that a packet */ /* has been received and placed in a waiting buffer. */ /* */ /* In lieu of a more conventional "on-the-fly" DMA mapping strategy in */ /* which skbs are DMA mapped and immediately unmapped when the transmit */ /* or receive has been completed, the VNIC driver is required to use */ /* "long term mapping". This entails that large, continuous DMA mapped */ /* buffers are allocated on driver initialization and these buffers are */ /* then continuously reused to pass skbs to and from the VNIC server. */ /* */ /**************************************************************************/
staticconstchar ibmvnic_driver_name[] = "ibmvnic"; staticconstchar ibmvnic_driver_string[] = "IBM System i/p Virtual NIC Driver";
MODULE_AUTHOR("Santiago Leon");
MODULE_DESCRIPTION("IBM System i/p Virtual NIC Driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(IBMVNIC_DRIVER_VERSION);
netdev_dbg(adapter->netdev, "%s: Cleaning irq affinity hints", __func__); if (txqs) { for (i = 0; i < num_txqs; i++)
ibmvnic_clean_queue_affinity(adapter, txqs[i]);
} if (rxqs) { for (i = 0; i < num_rxqs; i++)
ibmvnic_clean_queue_affinity(adapter, rxqs[i]);
}
}
staticint ibmvnic_set_queue_affinity(struct ibmvnic_sub_crq_queue *queue, unsignedint *cpu, int *stragglers, int stride)
{
cpumask_var_t mask; int i; int rc = 0;
if (!(queue && queue->irq)) return rc;
/* cpumask_var_t is either a pointer or array, allocation works here */ if (!zalloc_cpumask_var(&mask, GFP_KERNEL)) return -ENOMEM;
/* while we have extra cpu give one extra to this irq */ if (*stragglers) {
stride++;
(*stragglers)--;
} /* atomic write is safer than writing bit by bit directly */
for_each_online_cpu_wrap(i, *cpu) { if (!stride--) { /* For the next queue we start from the first * unused CPU in this queue
*/
*cpu = i; break;
}
cpumask_set_cpu(i, mask);
}
/** * ibmvnic_wait_for_completion - Check device state and wait for completion * @adapter: private device data * @comp_done: completion structure to wait for * @timeout: time to wait in milliseconds * * Wait for a completion signal or until the timeout limit is reached * while checking that the device is still active.
*/ staticint ibmvnic_wait_for_completion(struct ibmvnic_adapter *adapter, struct completion *comp_done, unsignedlong timeout)
{ struct net_device *netdev; unsignedlong div_timeout;
u8 retry;
netdev = adapter->netdev;
retry = 5;
div_timeout = msecs_to_jiffies(timeout / retry); while (true) { if (!adapter->crq.active) {
netdev_err(netdev, "Device down!\n"); return -ENODEV;
} if (!retry--) break; if (wait_for_completion_timeout(comp_done, div_timeout)) return 0;
}
netdev_err(netdev, "Operation timed out.\n"); return -ETIMEDOUT;
}
/** * reuse_ltb() - Check if a long term buffer can be reused * @ltb: The long term buffer to be checked * @size: The size of the long term buffer. * * An LTB can be reused unless its size has changed. * * Return: Return true if the LTB can be reused, false otherwise.
*/ staticbool reuse_ltb(struct ibmvnic_long_term_buff *ltb, int size)
{ return (ltb->buff && ltb->size == size);
}
/** * alloc_long_term_buff() - Allocate a long term buffer (LTB) * * @adapter: ibmvnic adapter associated to the LTB * @ltb: container object for the LTB * @size: size of the LTB * * Allocate an LTB of the specified size and notify VIOS. * * If the given @ltb already has the correct size, reuse it. Otherwise if * its non-NULL, free it. Then allocate a new one of the correct size. * Notify the VIOS either way since we may now be working with a new VIOS. * * Allocating larger chunks of memory during resets, specially LPM or under * low memory situations can cause resets to fail/timeout and for LPAR to * lose connectivity. So hold onto the LTB even if we fail to communicate * with the VIOS and reuse it on next open. Free LTB when adapter is closed. * * Return: 0 if we were able to allocate the LTB and notify the VIOS and * a negative value otherwise.
*/ staticint alloc_long_term_buff(struct ibmvnic_adapter *adapter, struct ibmvnic_long_term_buff *ltb, int size)
{ struct device *dev = &adapter->vdev->dev;
u64 prev = 0; int rc;
if (!reuse_ltb(ltb, size)) {
dev_dbg(dev, "LTB size changed from 0x%llx to 0x%x, reallocating\n",
ltb->size, size);
prev = ltb->size;
free_long_term_buff(adapter, ltb);
}
if (ltb->buff) {
dev_dbg(dev, "Reusing LTB [map %d, size 0x%llx]\n",
ltb->map_id, ltb->size);
} else {
ltb->buff = dma_alloc_coherent(dev, size, <b->addr,
GFP_KERNEL); if (!ltb->buff) {
dev_err(dev, "Couldn't alloc long term buffer\n"); return -ENOMEM;
}
ltb->size = size;
/* VIOS automatically unmaps the long term buffer at remote * end for the following resets: * FAILOVER, MOBILITY, TIMEOUT.
*/ if (adapter->reset_reason != VNIC_RESET_FAILOVER &&
adapter->reset_reason != VNIC_RESET_MOBILITY &&
adapter->reset_reason != VNIC_RESET_TIMEOUT)
send_request_unmap(adapter, ltb->map_id);
ltb->buff = NULL; /* mark this map_id free */
bitmap_clear(adapter->map_ids, ltb->map_id, 1);
ltb->map_id = 0;
}
/** * free_ltb_set - free the given set of long term buffers (LTBS) * @adapter: The ibmvnic adapter containing this ltb set * @ltb_set: The ltb_set to be freed * * Free the set of LTBs in the given set.
*/
staticvoid free_ltb_set(struct ibmvnic_adapter *adapter, struct ibmvnic_ltb_set *ltb_set)
{ int i;
for (i = 0; i < ltb_set->num_ltbs; i++)
free_long_term_buff(adapter, <b_set->ltbs[i]);
/** * alloc_ltb_set() - Allocate a set of long term buffers (LTBs) * * @adapter: ibmvnic adapter associated to the LTB * @ltb_set: container object for the set of LTBs * @num_buffs: Number of buffers in the LTB * @buff_size: Size of each buffer in the LTB * * Allocate a set of LTBs to accommodate @num_buffs buffers of @buff_size * each. We currently cap size each LTB to IBMVNIC_ONE_LTB_SIZE. If the * new set of LTBs have fewer LTBs than the old set, free the excess LTBs. * If new set needs more than in old set, allocate the remaining ones. * Try and reuse as many LTBs as possible and avoid reallocation. * * Any changes to this allocation strategy must be reflected in * map_rxpool_buff_to_ltb() and map_txpool_buff_to_ltb().
*/ staticint alloc_ltb_set(struct ibmvnic_adapter *adapter, struct ibmvnic_ltb_set *ltb_set, int num_buffs, int buff_size)
{ struct device *dev = &adapter->vdev->dev; struct ibmvnic_ltb_set old_set; struct ibmvnic_ltb_set new_set; int rem_size; int tot_size; /* size of all ltbs */ int ltb_size; /* size of one ltb */ int nltbs; int rc; int n; int i;
nltbs = tot_size / ltb_size; if (tot_size % ltb_size)
nltbs++;
old_set = *ltb_set;
if (old_set.num_ltbs == nltbs) {
new_set = old_set;
} else { int tmp = nltbs * sizeof(struct ibmvnic_long_term_buff);
new_set.ltbs = kzalloc(tmp, GFP_KERNEL); if (!new_set.ltbs) return -ENOMEM;
new_set.num_ltbs = nltbs;
/* Free any excess ltbs in old set */ for (i = new_set.num_ltbs; i < old_set.num_ltbs; i++)
free_long_term_buff(adapter, &old_set.ltbs[i]);
/* Copy remaining ltbs to new set. All LTBs except the * last one are of the same size. alloc_long_term_buff() * will realloc if the size changes.
*/
n = min(old_set.num_ltbs, new_set.num_ltbs); for (i = 0; i < n; i++)
new_set.ltbs[i] = old_set.ltbs[i];
/* Any additional ltbs in new set will have NULL ltbs for * now and will be allocated in alloc_long_term_buff().
*/
/* We no longer need the old_set so free it. Note that we * may have reused some ltbs from old set and freed excess * ltbs above. So we only need to free the container now * not the LTBs themselves. (i.e. dont free_ltb_set()!)
*/
kfree(old_set.ltbs);
old_set.ltbs = NULL;
old_set.num_ltbs = 0;
/* Install the new set. If allocations fail below, we will * retry later and know what size LTBs we need.
*/
*ltb_set = new_set;
}
i = 0;
rem_size = tot_size; while (rem_size) { if (ltb_size > rem_size)
ltb_size = rem_size;
return 0;
out: /* We may have allocated one/more LTBs before failing and we * want to try and reuse on next reset. So don't free ltb set.
*/ return rc;
}
/** * map_rxpool_buf_to_ltb - Map given rxpool buffer to offset in an LTB. * @rxpool: The receive buffer pool containing buffer * @bufidx: Index of buffer in rxpool * @ltbp: (Output) pointer to the long term buffer containing the buffer * @offset: (Output) offset of buffer in the LTB from @ltbp * * Map the given buffer identified by [rxpool, bufidx] to an LTB in the * pool and its corresponding offset. Assume for now that each LTB is of * different size but could possibly be optimized based on the allocation * strategy in alloc_ltb_set().
*/ staticvoid map_rxpool_buf_to_ltb(struct ibmvnic_rx_pool *rxpool, unsignedint bufidx, struct ibmvnic_long_term_buff **ltbp, unsignedint *offset)
{ struct ibmvnic_long_term_buff *ltb; int nbufs; /* # of buffers in one ltb */ int i;
WARN_ON(bufidx >= rxpool->size);
for (i = 0; i < rxpool->ltb_set.num_ltbs; i++) {
ltb = &rxpool->ltb_set.ltbs[i];
nbufs = ltb->size / rxpool->buff_size; if (bufidx < nbufs) break;
bufidx -= nbufs;
}
/** * map_txpool_buf_to_ltb - Map given txpool buffer to offset in an LTB. * @txpool: The transmit buffer pool containing buffer * @bufidx: Index of buffer in txpool * @ltbp: (Output) pointer to the long term buffer (LTB) containing the buffer * @offset: (Output) offset of buffer in the LTB from @ltbp * * Map the given buffer identified by [txpool, bufidx] to an LTB in the * pool and its corresponding offset.
*/ staticvoid map_txpool_buf_to_ltb(struct ibmvnic_tx_pool *txpool, unsignedint bufidx, struct ibmvnic_long_term_buff **ltbp, unsignedint *offset)
{ struct ibmvnic_long_term_buff *ltb; int nbufs; /* # of buffers in one ltb */ int i;
WARN_ON_ONCE(bufidx >= txpool->num_buffers);
for (i = 0; i < txpool->ltb_set.num_ltbs; i++) {
ltb = &txpool->ltb_set.ltbs[i];
nbufs = ltb->size / txpool->buf_size; if (bufidx < nbufs) break;
bufidx -= nbufs;
}
/* netdev_skb_alloc() could have failed after we saved a few skbs * in the indir_buf and we would not have sent them to VIOS yet. * To account for them, start the loop at ind_bufp->index rather * than 0. If we pushed all the skbs to VIOS, ind_bufp->index will * be 0.
*/ for (i = ind_bufp->index; i < count; ++i) {
bufidx = pool->free_map[pool->next_free];
/* We maybe reusing the skb from earlier resets. Allocate * only if necessary. But since the LTB may have changed * during reset (see init_rx_pools()), update LTB below * even if reusing skb.
*/
skb = pool->rx_buff[bufidx].skb; if (!skb) {
skb = netdev_alloc_skb(adapter->netdev,
pool->buff_size); if (!skb) {
dev_err(dev, "Couldn't replenish rx buff\n");
adapter->replenish_no_mem++; break;
}
}
/* Copy the skb to the long term mapped DMA buffer */
map_rxpool_buf_to_ltb(pool, bufidx, <b, &offset);
dst = ltb->buff + offset;
memset(dst, 0, pool->buff_size);
dma_addr = ltb->addr + offset;
/* add the skb to an rx_buff in the pool */
pool->rx_buff[bufidx].data = dst;
pool->rx_buff[bufidx].dma = dma_addr;
pool->rx_buff[bufidx].skb = skb;
pool->rx_buff[bufidx].pool_index = pool->index;
pool->rx_buff[bufidx].size = pool->buff_size;
/* queue the rx_buff for the next send_subcrq_indirect */
sub_crq = &ind_bufp->indir_arr[ind_bufp->index++];
memset(sub_crq, 0, sizeof(*sub_crq));
sub_crq->rx_add.first = IBMVNIC_CRQ_CMD;
sub_crq->rx_add.correlator =
cpu_to_be64((u64)&pool->rx_buff[bufidx]);
sub_crq->rx_add.ioba = cpu_to_be32(dma_addr);
sub_crq->rx_add.map_id = ltb->map_id;
/* The length field of the sCRQ is defined to be 24 bits so the * buffer size needs to be left shifted by a byte before it is * converted to big endian to prevent the last byte from being * truncated.
*/ #ifdef __LITTLE_ENDIAN__
shift = 8; #endif
sub_crq->rx_add.len = cpu_to_be32(pool->buff_size << shift);
/* if send_subcrq_indirect queue is full, flush to VIOS */ if (ind_bufp->index == IBMVNIC_MAX_IND_DESCS ||
i == count - 1) {
lpar_rc =
send_subcrq_indirect(adapter, handle,
(u64)ind_bufp->indir_dma,
(u64)ind_bufp->index); if (lpar_rc != H_SUCCESS) goto failure;
buffers_added += ind_bufp->index;
adapter->replenish_add_buff_success += ind_bufp->index;
ind_bufp->index = 0;
}
}
atomic_add(buffers_added, &pool->available); return;
failure: if (lpar_rc != H_PARAMETER && lpar_rc != H_CLOSED)
dev_err_ratelimited(dev, "rx: replenish packet buffer failed\n"); for (i = ind_bufp->index - 1; i >= 0; --i) { struct ibmvnic_rx_buff *rx_buff;
pool->next_free = pool->next_free == 0 ?
pool->size - 1 : pool->next_free - 1;
sub_crq = &ind_bufp->indir_arr[i];
rx_buff = (struct ibmvnic_rx_buff *)
be64_to_cpu(sub_crq->rx_add.correlator);
bufidx = (int)(rx_buff - pool->rx_buff);
pool->free_map[pool->next_free] = bufidx;
dev_kfree_skb_any(pool->rx_buff[bufidx].skb);
pool->rx_buff[bufidx].skb = NULL;
}
adapter->replenish_add_buff_failure += ind_bufp->index;
atomic_add(buffers_added, &pool->available);
ind_bufp->index = 0; if (lpar_rc == H_CLOSED || adapter->failover_pending) { /* Disable buffer pool replenishment and report carrier off if * queue is closed or pending failover. * Firmware guarantees that a signal will be sent to the * driver, triggering a reset.
*/
deactivate_rx_pools(adapter);
netif_carrier_off(adapter->netdev);
}
}
staticvoid replenish_pools(struct ibmvnic_adapter *adapter)
{ int i;
adapter->replenish_task_cycles++; for (i = 0; i < adapter->num_active_rx_pools; i++) { if (adapter->rx_pool[i].active)
replenish_rx_pool(adapter, &adapter->rx_pool[i]);
}
/** * release_rx_pools() - Release any rx pools attached to @adapter. * @adapter: ibmvnic adapter * * Safe to call this multiple times - even if no pools are attached.
*/ staticvoid release_rx_pools(struct ibmvnic_adapter *adapter)
{ struct ibmvnic_rx_pool *rx_pool; int i, j;
if (!adapter->rx_pool) return;
for (i = 0; i < adapter->num_active_rx_pools; i++) {
rx_pool = &adapter->rx_pool[i];
/** * reuse_rx_pools() - Check if the existing rx pools can be reused. * @adapter: ibmvnic adapter * * Check if the existing rx pools in the adapter can be reused. The * pools can be reused if the pool parameters (number of pools, * number of buffers in the pool and size of each buffer) have not * changed. * * NOTE: This assumes that all pools have the same number of buffers * which is the case currently. If that changes, we must fix this. * * Return: true if the rx pools can be reused, false otherwise.
*/ staticbool reuse_rx_pools(struct ibmvnic_adapter *adapter)
{
u64 old_num_pools, new_num_pools;
u64 old_pool_size, new_pool_size;
u64 old_buff_size, new_buff_size;
/** * init_rx_pools(): Initialize the set of receiver pools in the adapter. * @netdev: net device associated with the vnic interface * * Initialize the set of receiver pools in the ibmvnic adapter associated * with the net_device @netdev. If possible, reuse the existing rx pools. * Otherwise free any existing pools and allocate a new set of pools * before initializing them. * * Return: 0 on success and negative value on error.
*/ staticint init_rx_pools(struct net_device *netdev)
{ struct ibmvnic_adapter *adapter = netdev_priv(netdev); struct device *dev = &adapter->vdev->dev; struct ibmvnic_rx_pool *rx_pool;
u64 num_pools;
u64 pool_size; /* # of buffers in one pool */
u64 buff_size; int i, j, rc;
if (reuse_rx_pools(adapter)) {
dev_dbg(dev, "Reusing rx pools\n"); goto update_ltb;
}
/* Allocate/populate the pools. */
release_rx_pools(adapter);
adapter->rx_pool = kcalloc(num_pools, sizeof(struct ibmvnic_rx_pool),
GFP_KERNEL); if (!adapter->rx_pool) {
dev_err(dev, "Failed to allocate rx pools\n"); return -ENOMEM;
}
/* Set num_active_rx_pools early. If we fail below after partial * allocation, release_rx_pools() will know how many to look for.
*/
adapter->num_active_rx_pools = num_pools;
for (i = 0; i < num_pools; i++) {
rx_pool = &adapter->rx_pool[i];
update_ltb: for (i = 0; i < num_pools; i++) {
rx_pool = &adapter->rx_pool[i];
dev_dbg(dev, "Updating LTB for rx pool %d [%d, %d]\n",
i, rx_pool->size, rx_pool->buff_size);
rc = alloc_ltb_set(adapter, &rx_pool->ltb_set,
rx_pool->size, rx_pool->buff_size); if (rc) goto out;
/* NOTE: Don't clear rx_buff->skb here - will leak * memory! replenish_rx_pool() will reuse skbs or * allocate as necessary.
*/
rx_buff = &rx_pool->rx_buff[j];
rx_buff->dma = 0;
rx_buff->data = 0;
rx_buff->size = 0;
rx_buff->pool_index = 0;
}
/* Mark pool "empty" so replenish_rx_pools() will * update the LTB info for each buffer
*/
atomic_set(&rx_pool->available, 0);
rx_pool->next_alloc = 0;
rx_pool->next_free = 0; /* replenish_rx_pool() may have called deactivate_rx_pools() * on failover. Ensure pool is active now.
*/
rx_pool->active = 1;
} return 0;
out_release:
release_rx_pools(adapter);
out: /* We failed to allocate one or more LTBs or map them on the VIOS. * Hold onto the pools and any LTBs that we did allocate/map.
*/ return rc;
}
staticvoid release_vpd_data(struct ibmvnic_adapter *adapter)
{ if (!adapter->vpd) return;
/** * release_tx_pools() - Release any tx pools attached to @adapter. * @adapter: ibmvnic adapter * * Safe to call this multiple times - even if no pools are attached.
*/ staticvoid release_tx_pools(struct ibmvnic_adapter *adapter)
{ int i;
/* init_tx_pools() ensures that ->tx_pool and ->tso_pool are * both NULL or both non-NULL. So we only need to check one.
*/ if (!adapter->tx_pool) return;
for (i = 0; i < adapter->num_active_tx_pools; i++) {
release_one_tx_pool(adapter, &adapter->tx_pool[i]);
release_one_tx_pool(adapter, &adapter->tso_pool[i]);
}
/** * reuse_tx_pools() - Check if the existing tx pools can be reused. * @adapter: ibmvnic adapter * * Check if the existing tx pools in the adapter can be reused. The * pools can be reused if the pool parameters (number of pools, * number of buffers in the pool and mtu) have not changed. * * NOTE: This assumes that all pools have the same number of buffers * which is the case currently. If that changes, we must fix this. * * Return: true if the tx pools can be reused, false otherwise.
*/ staticbool reuse_tx_pools(struct ibmvnic_adapter *adapter)
{
u64 old_num_pools, new_num_pools;
u64 old_pool_size, new_pool_size;
u64 old_mtu, new_mtu;
/** * init_tx_pools(): Initialize the set of transmit pools in the adapter. * @netdev: net device associated with the vnic interface * * Initialize the set of transmit pools in the ibmvnic adapter associated * with the net_device @netdev. If possible, reuse the existing tx pools. * Otherwise free any existing pools and allocate a new set of pools * before initializing them. * * Return: 0 on success and negative value on error.
*/ staticint init_tx_pools(struct net_device *netdev)
{ struct ibmvnic_adapter *adapter = netdev_priv(netdev); struct device *dev = &adapter->vdev->dev; int num_pools;
u64 pool_size; /* # of buffers in pool */
u64 buff_size; int i, j, rc;
num_pools = adapter->req_tx_queues;
/* We must notify the VIOS about the LTB on all resets - but we only * need to alloc/populate pools if either the number of buffers or * size of each buffer in the pool has changed.
*/ if (reuse_tx_pools(adapter)) {
netdev_dbg(netdev, "Reusing tx pools\n"); goto update_ltb;
}
/* Allocate/populate the pools. */
release_tx_pools(adapter);
adapter->tx_pool = kcalloc(num_pools, sizeof(struct ibmvnic_tx_pool), GFP_KERNEL); if (!adapter->tx_pool) return -ENOMEM;
adapter->tso_pool = kcalloc(num_pools, sizeof(struct ibmvnic_tx_pool), GFP_KERNEL); /* To simplify release_tx_pools() ensure that ->tx_pool and * ->tso_pool are either both NULL or both non-NULL.
*/ if (!adapter->tso_pool) {
kfree(adapter->tx_pool);
adapter->tx_pool = NULL; return -ENOMEM;
}
/* Set num_active_tx_pools early. If we fail below after partial * allocation, release_tx_pools() will know how many to look for.
*/
adapter->num_active_tx_pools = num_pools;
update_ltb: /* NOTE: All tx_pools have the same number of buffers (which is * same as pool_size). All tso_pools have IBMVNIC_TSO_BUFS * buffers (see calls init_one_tx_pool() for these). * For consistency, we use tx_pool->num_buffers and * tso_pool->num_buffers below.
*/
rc = -1; for (i = 0; i < num_pools; i++) { struct ibmvnic_tx_pool *tso_pool; struct ibmvnic_tx_pool *tx_pool;
tx_pool = &adapter->tx_pool[i];
dev_dbg(dev, "Updating LTB for tx pool %d [%d, %d]\n",
i, tx_pool->num_buffers, tx_pool->buf_size);
rc = alloc_ltb_set(adapter, &tx_pool->ltb_set,
tx_pool->num_buffers, tx_pool->buf_size); if (rc) goto out;
return 0;
out_release:
release_tx_pools(adapter);
out: /* We failed to allocate one or more LTBs or map them on the VIOS. * Hold onto the pools and any LTBs that we did allocate/map.
*/ return rc;
}
staticvoid ibmvnic_napi_enable(struct ibmvnic_adapter *adapter)
{ int i;
if (adapter->napi_enabled) return;
for (i = 0; i < adapter->req_rx_queues; i++)
napi_enable(&adapter->napi[i]);
adapter->napi_enabled = true;
}
staticvoid ibmvnic_napi_disable(struct ibmvnic_adapter *adapter)
{ int i;
if (!adapter->napi_enabled) return;
for (i = 0; i < adapter->req_rx_queues; i++) {
netdev_dbg(adapter->netdev, "Disabling napi[%d]\n", i);
napi_disable(&adapter->napi[i]);
}
adapter->napi_enabled = false;
}
staticint init_napi(struct ibmvnic_adapter *adapter)
{ int i;
adapter->napi = kcalloc(adapter->req_rx_queues, sizeof(struct napi_struct), GFP_KERNEL); if (!adapter->napi) return -ENOMEM;
for (i = 0; i < adapter->req_rx_queues; i++) {
netdev_dbg(adapter->netdev, "Adding napi[%d]\n", i);
netif_napi_add(adapter->netdev, &adapter->napi[i],
ibmvnic_poll);
}
staticconstchar *adapter_state_to_string(enum vnic_state state)
{ switch (state) { case VNIC_PROBING: return"PROBING"; case VNIC_PROBED: return"PROBED"; case VNIC_OPENING: return"OPENING"; case VNIC_OPEN: return"OPEN"; case VNIC_CLOSING: return"CLOSING"; case VNIC_CLOSED: return"CLOSED"; case VNIC_REMOVING: return"REMOVING"; case VNIC_REMOVED: return"REMOVED"; case VNIC_DOWN: return"DOWN";
} return"UNKNOWN";
}
staticint ibmvnic_login(struct net_device *netdev)
{ unsignedlong flags, timeout = msecs_to_jiffies(20000); struct ibmvnic_adapter *adapter = netdev_priv(netdev); int retry_count = 0; int retries = 10; bool retry; int rc;
do {
retry = false; if (retry_count > retries) {
netdev_warn(netdev, "Login attempts exceeded\n"); return -EACCES;
}
partial_reset: /* adapter login failed, so free any CRQs or sub-CRQs * and register again before attempting to login again. * If we don't do this then the VIOS may think that * we are already logged in and reject any subsequent * attempts
*/
netdev_warn(netdev, "Freeing and re-registering CRQs before attempting to login again\n");
retry = true;
adapter->init_done_rc = 0;
release_sub_crqs(adapter, true); /* Much of this is similar logic as ibmvnic_probe(), * we are essentially re-initializing communication * with the server. We really should not run any * resets/failovers here because this is already a form * of reset and we do not want parallel resets occurring
*/ do {
reinit_init_done(adapter); /* Clear any failovers we got in the previous * pass since we are re-initializing the CRQ
*/
adapter->failover_pending = false;
release_crq_queue(adapter); /* If we don't sleep here then we risk an * unnecessary failover event from the VIOS. * This is a known VIOS issue caused by a vnic * device freeing and registering a CRQ too * quickly.
*/
msleep(1500); /* Avoid any resets, since we are currently * resetting.
*/
spin_lock_irqsave(&adapter->rwi_lock, flags);
flush_reset_queue(adapter);
spin_unlock_irqrestore(&adapter->rwi_lock,
flags);
rc = ibmvnic_reset_init(adapter, false); if (rc)
netdev_err(netdev, "login recovery: Reset init failed %d\n",
rc); /* IBMVNIC_CRQ_INIT will return EAGAIN if it * fails, since ibmvnic_reset_init will free * irq's in failure, we won't be able to receive * new CRQs so we need to keep trying. probe() * handles this similarly.
*/
} while (rc == -EAGAIN && retry_count++ < retries);
}
} while (retry);
netdev_dbg(netdev, "Setting real tx/rx queues (%llx/%llx)\n",
adapter->req_tx_queues, adapter->req_rx_queues);
rc = netif_set_real_num_tx_queues(netdev, adapter->req_tx_queues); if (rc) {
netdev_err(netdev, "failed to set the number of tx queues\n"); return rc;
}
rc = netif_set_real_num_rx_queues(netdev, adapter->req_rx_queues); if (rc)
netdev_err(netdev, "failed to set the number of rx queues\n");
return rc;
}
staticint ibmvnic_get_vpd(struct ibmvnic_adapter *adapter)
{ struct device *dev = &adapter->vdev->dev; union ibmvnic_crq crq; int len = 0; int rc;
/* We're ready to receive frames, enable the sub-crq interrupts and * set the logical link state to up
*/ for (i = 0; i < adapter->req_rx_queues; i++) {
netdev_dbg(netdev, "Enabling rx_scrq[%d] irq\n", i); if (prev_state == VNIC_CLOSED)
enable_irq(adapter->rx_scrq[i]->irq);
enable_scrq_irq(adapter, adapter->rx_scrq[i]);
}
for (i = 0; i < adapter->req_tx_queues; i++) {
netdev_dbg(netdev, "Enabling tx_scrq[%d] irq\n", i); if (prev_state == VNIC_CLOSED)
enable_irq(adapter->tx_scrq[i]->irq);
enable_scrq_irq(adapter, adapter->tx_scrq[i]); /* netdev_tx_reset_queue will reset dql stats. During NON_FATAL * resets, don't reset the stats because there could be batched * skb's waiting to be sent. If we reset dql stats, we risk * num_completed being greater than num_queued. This will cause * a BUG_ON in dql_completed().
*/ if (adapter->reset_reason != VNIC_RESET_NON_FATAL)
netdev_tx_reset_queue(netdev_get_tx_queue(netdev, i));
}
/* Since queues were stopped until now, there shouldn't be any * one in ibmvnic_complete_tx() or ibmvnic_xmit() so maybe we * don't need the synchronize_rcu()? Leaving it for consistency * with setting ->tx_queues_active = false.
*/
synchronize_rcu();
netif_tx_start_all_queues(netdev);
if (prev_state == VNIC_CLOSED) { for (i = 0; i < adapter->req_rx_queues; i++)
napi_schedule(&adapter->napi[i]);
}
/* If device failover is pending or we are about to reset, just set * device state and return. Device operation will be handled by reset * routine. * * It should be safe to overwrite the adapter->state here. Since * we hold the rtnl, either the reset has not actually started or * the rtnl got dropped during the set_link_state() in do_reset(). * In the former case, no one else is changing the state (again we * have the rtnl) and in the latter case, do_reset() will detect and * honor our setting below.
*/ if (adapter->failover_pending || (test_bit(0, &adapter->resetting))) {
netdev_dbg(netdev, "[S:%s FOP:%d] Resetting, deferring open\n",
adapter_state_to_string(adapter->state),
adapter->failover_pending);
adapter->state = VNIC_OPEN;
rc = 0; goto out;
}
if (adapter->state != VNIC_CLOSED) {
rc = ibmvnic_login(netdev); if (rc) goto out;
rc = init_resources(adapter); if (rc) {
netdev_err(netdev, "failed to initialize resources\n"); goto out;
}
}
rc = __ibmvnic_open(netdev);
out: /* If open failed and there is a pending failover or in-progress reset, * set device state and return. Device operation will be handled by * reset routine. See also comments above regarding rtnl.
*/ if (rc &&
(adapter->failover_pending || (test_bit(0, &adapter->resetting)))) {
adapter->state = VNIC_OPEN;
rc = 0;
}
if (rc) {
release_resources(adapter);
release_rx_pools(adapter);
release_tx_pools(adapter);
}
return rc;
}
staticvoid clean_rx_pools(struct ibmvnic_adapter *adapter)
{ struct ibmvnic_rx_pool *rx_pool; struct ibmvnic_rx_buff *rx_buff;
u64 rx_entries; int rx_scrqs; int i, j;
/* Free any remaining skbs in the rx buffer pools */ for (i = 0; i < rx_scrqs; i++) {
rx_pool = &adapter->rx_pool[i]; if (!rx_pool || !rx_pool->rx_buff) continue;
for (i = 0; i < tx_entries; i++) {
tx_buff = &tx_pool->tx_buff[i]; if (tx_buff && tx_buff->skb) {
dev_kfree_skb_any(tx_buff->skb);
tx_buff->skb = NULL;
}
}
}
staticvoid clean_tx_pools(struct ibmvnic_adapter *adapter)
{ int tx_scrqs; int i;
if (!adapter->tx_pool || !adapter->tso_pool) return;
tx_scrqs = adapter->num_active_tx_pools;
/* Free any remaining skbs in the tx buffer pools */ for (i = 0; i < tx_scrqs; i++) {
netdev_dbg(adapter->netdev, "Cleaning tx_pool[%d]\n", i);
clean_one_tx_pool(adapter, &adapter->tx_pool[i]);
clean_one_tx_pool(adapter, &adapter->tso_pool[i]);
}
}
if (adapter->tx_scrq) { for (i = 0; i < adapter->req_tx_queues; i++) if (adapter->tx_scrq[i]->irq) {
netdev_dbg(netdev, "Disabling tx_scrq[%d] irq\n", i);
disable_scrq_irq(adapter, adapter->tx_scrq[i]);
disable_irq(adapter->tx_scrq[i]->irq);
}
}
if (adapter->rx_scrq) { for (i = 0; i < adapter->req_rx_queues; i++) { if (adapter->rx_scrq[i]->irq) {
netdev_dbg(netdev, "Disabling rx_scrq[%d] irq\n", i);
disable_scrq_irq(adapter, adapter->rx_scrq[i]);
disable_irq(adapter->rx_scrq[i]->irq);
}
}
}
}
/* If device failover is pending, just set device state and return. * Device operation will be handled by reset routine.
*/ if (adapter->failover_pending) {
adapter->state = VNIC_CLOSED; return 0;
}
/** * get_hdr_lens - fills list of L2/L3/L4 hdr lens * @hdr_field: bitfield determining needed headers * @skb: socket buffer * @hdr_len: array of header lengths to be filled * * Reads hdr_field to determine which headers are needed by firmware. * Builds a buffer containing these headers. Saves individual header * lengths and total buffer length to be used to build descriptors. * * Return: total len of all headers
*/ staticint get_hdr_lens(u8 hdr_field, struct sk_buff *skb, int *hdr_len)
{ int len = 0;
if ((hdr_field >> 6) & 1) {
hdr_len[0] = skb_mac_header_len(skb);
len += hdr_len[0];
}
if ((hdr_field >> 5) & 1) {
hdr_len[1] = skb_network_header_len(skb);
len += hdr_len[1];
}
/** * create_hdr_descs - create header and header extension descriptors * @hdr_field: bitfield determining needed headers * @hdr_data: buffer containing header data * @len: length of data buffer * @hdr_len: array of individual header lengths * @scrq_arr: descriptor array * * Creates header and, if needed, header extension descriptors and * places them in a descriptor array, scrq_arr * * Return: Number of header descs
*/
staticint create_hdr_descs(u8 hdr_field, u8 *hdr_data, int len, int *hdr_len, union sub_crq *scrq_arr)
{ union sub_crq *hdr_desc; int tmp_len = len; int num_descs = 0;
u8 *data, *cur; int tmp;
while (tmp_len > 0) {
cur = hdr_data + len - tmp_len;
/** * build_hdr_descs_arr - build a header descriptor array * @skb: tx socket buffer * @indir_arr: indirect array * @num_entries: number of descriptors to be sent * @hdr_field: bit field determining which headers will be sent * * This function will build a TX descriptor array with applicable * L2/L3/L4 packet header descriptors to be sent by send_subcrq_indirect.
*/
staticvoid build_hdr_descs_arr(struct sk_buff *skb, union sub_crq *indir_arr, int *num_entries, u8 hdr_field)
{ int hdr_len[3] = {0, 0, 0}; int tot_len;
staticint ibmvnic_xmit_workarounds(struct sk_buff *skb, struct net_device *netdev)
{ /* For some backing devices, mishandling of small packets * can result in a loss of connection or TX stall. Device * architects recommend that no packet should be smaller * than the minimum MTU value provided to the driver, so * pad any packets to that length
*/ if (skb->len < netdev->min_mtu) return skb_put_padto(skb, netdev->min_mtu);
return 0;
}
staticvoid ibmvnic_tx_scrq_clean_buffer(struct ibmvnic_adapter *adapter, struct ibmvnic_sub_crq_queue *tx_scrq)
{ struct ibmvnic_ind_xmit_queue *ind_bufp; struct ibmvnic_tx_buff *tx_buff; struct ibmvnic_tx_pool *tx_pool; union sub_crq tx_scrq_entry; int queue_num; int entries; int index; int i;
staticint send_subcrq_direct(struct ibmvnic_adapter *adapter,
u64 remote_handle, u64 *entry)
{ unsignedint ua = adapter->vdev->unit_address; struct device *dev = &adapter->vdev->dev; int rc;
/* Make sure the hypervisor sees the complete request */
dma_wmb();
rc = plpar_hcall_norets(H_SEND_SUB_CRQ, ua,
cpu_to_be64(remote_handle),
cpu_to_be64(entry[0]), cpu_to_be64(entry[1]),
cpu_to_be64(entry[2]), cpu_to_be64(entry[3]));
/* If a reset is in progress, drop the packet since * the scrqs may get torn down. Otherwise use the * rcu to ensure reset waits for us to complete.
*/
rcu_read_lock(); if (!adapter->tx_queues_active) {
dev_kfree_skb_any(skb);
tx_send_failed++;
tx_dropped++;
ret = NETDEV_TX_OK; goto out;
}
/* if we are going to send_subcrq_direct this then we need to * update the checksum before copying the data into ltb. Essentially * these packets force disable CSO so that we can guarantee that * FW does not need header info and we can send direct. Also, vnic * server must be able to xmit standard packets without header data
*/ if (*hdrs == 0 && !skb_is_gso(skb) &&
!ind_bufp->index && !netdev_xmit_more()) {
use_scrq_send_direct = true; if (skb->ip_summed == CHECKSUM_PARTIAL &&
skb_checksum_help(skb))
use_scrq_send_direct = false;
}
if (skb_shinfo(skb)->nr_frags) { int cur, i;
/* Copy the head */
skb_copy_from_linear_data(skb, dst, skb_headlen(skb));
cur = skb_headlen(skb);
/* Copy the frags */ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
/* Sanity checks on our free map to make sure it points to an index * that is not being occupied by another skb. If skb memory is * not freed then we see congestion control kick in and halt tx.
*/ if (unlikely(tx_buff->skb)) {
dev_warn_ratelimited(dev, "TX free map points to untracked skb (%s %d idx=%d)\n",
skb_is_gso(skb) ? "tso_pool" : "tx_pool",
queue_num, bufidx);
dev_kfree_skb_any(tx_buff->skb);
}
if ((*hdrs >> 7) & 1)
build_hdr_descs_arr(skb, indir_arr, &num_entries, *hdrs);
tx_crq.v1.n_crq_elem = num_entries;
tx_buff->num_entries = num_entries; /* flush buffer if current entry can not fit */ if (num_entries + ind_bufp->index > IBMVNIC_MAX_IND_DESCS) {
lpar_rc = ibmvnic_tx_scrq_flush(adapter, tx_scrq, true); if (lpar_rc != H_SUCCESS) goto tx_flush_err;
}
if (lpar_rc == H_CLOSED || adapter->failover_pending) { /* Disable TX and report carrier off if queue is closed * or pending failover. * Firmware guarantees that a signal will be sent to the * driver, triggering a reset or some other action.
*/
netif_tx_stop_all_queues(netdev);
netif_carrier_off(netdev);
}
out:
rcu_read_unlock();
adapter->tx_send_failed += tx_send_failed;
adapter->tx_map_failed += tx_map_failed;
adapter->tx_stats_buffers[queue_num].batched_packets += tx_bpackets;
adapter->tx_stats_buffers[queue_num].direct_packets += tx_dpackets;
adapter->tx_stats_buffers[queue_num].bytes += tx_bytes;
adapter->tx_stats_buffers[queue_num].dropped_packets += tx_dropped;
rc = 0; if (!is_valid_ether_addr(addr->sa_data)) return -EADDRNOTAVAIL;
ether_addr_copy(adapter->mac_addr, addr->sa_data); if (adapter->state != VNIC_PROBED)
rc = __ibmvnic_set_mac(netdev, addr->sa_data);
return rc;
}
staticconstchar *reset_reason_to_string(enum ibmvnic_reset_reason reason)
{ switch (reason) { case VNIC_RESET_FAILOVER: return"FAILOVER"; case VNIC_RESET_MOBILITY: return"MOBILITY"; case VNIC_RESET_FATAL: return"FATAL"; case VNIC_RESET_NON_FATAL: return"NON_FATAL"; case VNIC_RESET_TIMEOUT: return"TIMEOUT"; case VNIC_RESET_CHANGE_PARAM: return"CHANGE_PARAM"; case VNIC_RESET_PASSIVE_INIT: return"PASSIVE_INIT";
} return"UNKNOWN";
}
/* * Initialize the init_done completion and return code values. We * can get a transport event just after registering the CRQ and the * tasklet will use this to communicate the transport event. To ensure * we don't miss the notification/error, initialize these _before_ * regisering the CRQ.
*/ staticinlinevoid reinit_init_done(struct ibmvnic_adapter *adapter)
{
reinit_completion(&adapter->init_done);
adapter->init_done_rc = 0;
}
/* * do_reset returns zero if we are able to keep processing reset events, or * non-zero if we hit a fatal error and must halt.
*/ staticint do_reset(struct ibmvnic_adapter *adapter, struct ibmvnic_rwi *rwi, u32 reset_state)
{ struct net_device *netdev = adapter->netdev;
u64 old_num_rx_queues, old_num_tx_queues;
u64 old_num_rx_slots, old_num_tx_slots; int rc;
adapter->reset_reason = rwi->reset_reason; /* requestor of VNIC_RESET_CHANGE_PARAM already has the rtnl lock */ if (!(adapter->reset_reason == VNIC_RESET_CHANGE_PARAM))
rtnl_lock();
/* Now that we have the rtnl lock, clear any pending failover. * This will ensure ibmvnic_open() has either completed or will * block until failover is complete.
*/ if (rwi->reset_reason == VNIC_RESET_FAILOVER)
adapter->failover_pending = false;
/* read the state and check (again) after getting rtnl */
reset_state = adapter->state;
/* Release the RTNL lock before link state change and * re-acquire after the link state change to allow * linkwatch_event to grab the RTNL lock and run during * a reset.
*/
rtnl_unlock();
rc = set_link_state(adapter, IBMVNIC_LOGICAL_LNK_DN);
rtnl_lock(); if (rc) goto out;
if (adapter->state == VNIC_OPEN) { /* When we dropped rtnl, ibmvnic_open() got * it and noticed that we are resetting and * set the adapter state to OPEN. Update our * new "target" state, and resume the reset * from VNIC_CLOSING state.
*/
netdev_dbg(netdev, "Open changed state from %s, updating.\n",
adapter_state_to_string(reset_state));
reset_state = VNIC_OPEN;
adapter->state = VNIC_CLOSING;
}
if (adapter->state != VNIC_CLOSING) { /* If someone else changed the adapter state * when we dropped the rtnl, fail the reset
*/
rc = -EAGAIN; goto out;
}
adapter->state = VNIC_CLOSED;
}
}
if (adapter->reset_reason == VNIC_RESET_CHANGE_PARAM) {
release_resources(adapter);
release_sub_crqs(adapter, 1);
release_crq_queue(adapter);
}
if (adapter->reset_reason != VNIC_RESET_NON_FATAL) { /* remove the closed state so when we call open it appears * we are coming from the probed state.
*/
adapter->state = VNIC_PROBED;
rc = ibmvnic_reset_init(adapter, true); if (rc) goto out;
/* If the adapter was in PROBE or DOWN state prior to the reset, * exit here.
*/ if (reset_state == VNIC_PROBED || reset_state == VNIC_DOWN) {
rc = 0; goto out;
}
/* refresh device's multicast list */
ibmvnic_set_multi(netdev);
if (adapter->reset_reason == VNIC_RESET_FAILOVER ||
adapter->reset_reason == VNIC_RESET_MOBILITY)
__netdev_notify_peers(netdev);
rc = 0;
out: /* restore the adapter state if reset failed */ if (rc)
adapter->state = reset_state; /* requestor of VNIC_RESET_CHANGE_PARAM should still hold the rtnl lock */ if (!(adapter->reset_reason == VNIC_RESET_CHANGE_PARAM))
rtnl_unlock();
/** * do_passive_init - complete probing when partner device is detected. * @adapter: ibmvnic_adapter struct * * If the ibmvnic device does not have a partner device to communicate with at boot * and that partner device comes online at a later time, this function is called * to complete the initialization process of ibmvnic device. * Caller is expected to hold rtnl_lock(). * * Returns non-zero if sub-CRQs are not initialized properly leaving the device * in the down state. * Returns 0 upon success and the device is in PROBED state.
*/
adapter = container_of(work, struct ibmvnic_adapter, ibmvnic_reset);
dev = &adapter->vdev->dev;
/* Wait for ibmvnic_probe() to complete. If probe is taking too long * or if another reset is in progress, defer work for now. If probe * eventually fails it will flush and terminate our work. * * Three possibilities here: * 1. Adpater being removed - just return * 2. Timed out on probe or another reset in progress - delay the work * 3. Completed probe - perform any resets in queue
*/ if (adapter->state == VNIC_PROBING &&
!wait_for_completion_timeout(&adapter->probe_done, timeout)) {
dev_err(dev, "Reset thread timed out on probe");
queue_delayed_work(system_long_wq,
&adapter->ibmvnic_delayed_reset,
IBMVNIC_RESET_DELAY); return;
}
/* adapter is done with probe (i.e state is never VNIC_PROBING now) */ if (adapter->state == VNIC_REMOVING) return;
/* ->rwi_list is stable now (no one else is removing entries) */
/* ibmvnic_probe() may have purged the reset queue after we were * scheduled to process a reset so there maybe no resets to process. * Before setting the ->resetting bit though, we have to make sure * that there is infact a reset to process. Otherwise we may race * with ibmvnic_open() and end up leaving the vnic down: * * __ibmvnic_reset() ibmvnic_open() * ----------------- -------------- * * set ->resetting bit * find ->resetting bit is set * set ->state to IBMVNIC_OPEN (i.e * assume reset will open device) * return * find reset queue empty * return * * Neither performed vnic login/open and vnic stays down * * If we hold the lock and conditionally set the bit, either we * or ibmvnic_open() will complete the open.
*/
need_reset = false;
spin_lock(&adapter->rwi_lock); if (!list_empty(&adapter->rwi_list)) { if (test_and_set_bit_lock(0, &adapter->resetting)) {
queue_delayed_work(system_long_wq,
&adapter->ibmvnic_delayed_reset,
IBMVNIC_RESET_DELAY);
} else {
need_reset = true;
}
}
spin_unlock(&adapter->rwi_lock);
if (!need_reset) return;
rwi = get_next_rwi(adapter); while (rwi) {
spin_lock_irqsave(&adapter->state_lock, flags);
if (rwi->reset_reason == VNIC_RESET_PASSIVE_INIT) {
rtnl_lock();
rc = do_passive_init(adapter);
rtnl_unlock(); if (!rc)
netif_carrier_on(adapter->netdev);
} elseif (adapter->force_reset_recovery) { /* Since we are doing a hard reset now, clear the * failover_pending flag so we don't ignore any * future MOBILITY or other resets.
*/
adapter->failover_pending = false;
/* Transport event occurred during previous reset */ if (adapter->wait_for_reset) { /* Previous was CHANGE_PARAM; caller locked */
adapter->force_reset_recovery = false;
rc = do_hard_reset(adapter, rwi, reset_state);
} else {
rtnl_lock();
adapter->force_reset_recovery = false;
rc = do_hard_reset(adapter, rwi, reset_state);
rtnl_unlock();
} if (rc)
num_fails++; else
num_fails = 0;
/* If auto-priority-failover is enabled we can get * back to back failovers during resets, resulting * in at least two failed resets (from high-priority * backing device to low-priority one and then back) * If resets continue to fail beyond that, give the * adapter some time to settle down before retrying.
*/ if (num_fails >= 3) {
netdev_dbg(adapter->netdev, "[S:%s] Hard reset failed %d times, waiting 60 secs\n",
adapter_state_to_string(adapter->state),
num_fails);
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(60 * HZ);
}
} else {
rc = do_reset(adapter, rwi, reset_state);
}
tmprwi = rwi;
adapter->last_reset_time = jiffies;
if (rc)
netdev_dbg(adapter->netdev, "Reset failed, rc=%d\n", rc);
rwi = get_next_rwi(adapter);
/* * If there are no resets queued and the previous reset failed, * the adapter would be in an undefined state. So retry the * previous reset as a hard reset. * * Else, free the previous rwi and, if there is another reset * queued, process the new reset even if previous reset failed * (the previous reset could have failed because of a fail * over for instance, so process the fail over).
*/ if (!rwi && rc)
rwi = tmprwi; else
kfree(tmprwi);
/* If failover is pending don't schedule any other reset. * Instead let the failover complete. If there is already a * a failover reset scheduled, we will detect and drop the * duplicate reset when walking the ->rwi_list below.
*/ if (adapter->state == VNIC_REMOVING ||
adapter->state == VNIC_REMOVED ||
(adapter->failover_pending && reason != VNIC_RESET_FAILOVER)) {
ret = EBUSY;
netdev_dbg(netdev, "Adapter removing or pending failover, skipping reset\n"); goto err;
}
rwi = kzalloc(sizeof(*rwi), GFP_ATOMIC); if (!rwi) {
ret = ENOMEM; goto err;
} /* if we just received a transport event, * flush reset queue and process this reset
*/ if (adapter->force_reset_recovery)
flush_reset_queue(adapter);
if (test_bit(0, &adapter->resetting)) {
netdev_err(adapter->netdev, "Adapter is resetting, skip timeout reset\n"); return;
} /* No queuing up reset until at least 5 seconds (default watchdog val) * after last reset
*/ if (time_before(jiffies, (adapter->last_reset_time + dev->watchdog_timeo))) {
netdev_dbg(dev, "Not yet time to tx timeout.\n"); return;
}
ibmvnic_reset(adapter, VNIC_RESET_TIMEOUT);
}
/* VLAN Header has been stripped by the system firmware and * needs to be inserted by the driver
*/ if (adapter->rx_vlan_header_insertion &&
(flags & IBMVNIC_VLAN_STRIPPED))
__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q),
ntohs(next->rx_comp.vlan_tci));
/* free the entry */
next->rx_comp.first = 0;
remove_buff_from_pool(adapter, rx_buff);
static netdev_features_t ibmvnic_features_check(struct sk_buff *skb, struct net_device *dev,
netdev_features_t features)
{ /* Some backing hardware adapters can not * handle packets with a MSS less than 224 * or with only one segment.
*/ if (skb_is_gso(skb)) { if (skb_shinfo(skb)->gso_size < 224 ||
skb_shinfo(skb)->gso_segs == 1)
features &= ~NETIF_F_GSO_MASK;
}
/* Don't need to send a query because we request a logical link up at * init and then we wait for link state indications
*/ return adapter->logical_link_state;
}
/* Wait for data to be written */
reinit_completion(&adapter->stats_done);
rc = ibmvnic_send_crq(adapter, &crq); if (rc) return;
rc = ibmvnic_wait_for_completion(adapter, &adapter->stats_done, 10000); if (rc) return;
for (i = 0; i < ARRAY_SIZE(ibmvnic_stats); i++)
data[i] = be64_to_cpu(IBMVNIC_GET_STAT
(adapter, ibmvnic_stats[i].offset));
if (do_h_free) { /* Close the sub-crqs */ do {
rc = plpar_hcall_norets(H_FREE_SUB_CRQ,
adapter->vdev->unit_address,
scrq->crq_num);
} while (rc == H_BUSY || H_IS_LONG_BUSY(rc));
if (rc) {
netdev_err(adapter->netdev, "Failed to release sub-CRQ %16lx, rc = %ld\n",
scrq->crq_num, rc);
}
}
/* We can not use the IRQ chip EOI handler because that has the * unintended effect of changing the interrupt priority.
*/ staticvoid ibmvnic_xics_eoi(struct device *dev, struct ibmvnic_sub_crq_queue *scrq)
{
u64 val = 0xff000000 | scrq->hw_irq; unsignedlong rc;
/* Due to a firmware bug, the hypervisor can send an interrupt to a * transmit or receive queue just prior to a partition migration. * Force an EOI after migration.
*/ staticvoid ibmvnic_clear_pending_interrupt(struct device *dev, struct ibmvnic_sub_crq_queue *scrq)
{ if (!xive_enabled())
ibmvnic_xics_eoi(dev, scrq);
}
restart_loop: while (pending_scrq(adapter, scrq)) { unsignedint pool = scrq->pool_index; int num_entries = 0;
next = ibmvnic_next_scrq(adapter, scrq); for (i = 0; i < next->tx_comp.num_comps; i++) {
index = be32_to_cpu(next->tx_comp.correlators[i]); if (index & IBMVNIC_TSO_POOL_MASK) {
tx_pool = &adapter->tso_pool[pool];
index &= ~IBMVNIC_TSO_POOL_MASK;
} else {
tx_pool = &adapter->tx_pool[pool];
}
/* When booting a kdump kernel we can hit pending interrupts * prior to completing driver initialization.
*/ if (unlikely(adapter->state != VNIC_OPEN)) return IRQ_NONE;
staticint init_sub_crqs(struct ibmvnic_adapter *adapter)
{ struct device *dev = &adapter->vdev->dev; struct ibmvnic_sub_crq_queue **allqueues; int registered_queues = 0; int total_queues; int more = 0; int i;
allqueues = kcalloc(total_queues, sizeof(*allqueues), GFP_KERNEL); if (!allqueues) return -ENOMEM;
for (i = 0; i < total_queues; i++) {
allqueues[i] = init_sub_crq_queue(adapter); if (!allqueues[i]) {
dev_warn(dev, "Couldn't allocate all sub-crqs\n"); break;
}
registered_queues++;
}
/* Make sure we were able to register the minimum number of queues */ if (registered_queues <
adapter->min_tx_queues + adapter->min_rx_queues) {
dev_err(dev, "Fatal: Couldn't init min number of sub-crqs\n"); goto tx_failed;
}
/* Distribute the failed allocated queues*/ for (i = 0; i < total_queues - registered_queues + more ; i++) {
netdev_dbg(adapter->netdev, "Reducing number of queues\n"); switch (i % 3) { case 0: if (adapter->req_rx_queues > adapter->min_rx_queues)
adapter->req_rx_queues--; else
more++; break; case 1: if (adapter->req_tx_queues > adapter->min_tx_queues)
adapter->req_tx_queues--; else
more++; break;
}
}
adapter->tx_scrq = kcalloc(adapter->req_tx_queues, sizeof(*adapter->tx_scrq), GFP_KERNEL); if (!adapter->tx_scrq) goto tx_failed;
for (i = 0; i < adapter->req_tx_queues; i++) {
adapter->tx_scrq[i] = allqueues[i];
adapter->tx_scrq[i]->pool_index = i;
adapter->num_active_tx_scrqs++;
}
adapter->rx_scrq = kcalloc(adapter->req_rx_queues, sizeof(*adapter->rx_scrq), GFP_KERNEL); if (!adapter->rx_scrq) goto rx_failed;
for (i = 0; i < adapter->req_rx_queues; i++) {
adapter->rx_scrq[i] = allqueues[i + adapter->req_tx_queues];
adapter->rx_scrq[i]->scrq_num = i;
adapter->num_active_rx_scrqs++;
}
kfree(allqueues); return 0;
rx_failed:
kfree(adapter->tx_scrq);
adapter->tx_scrq = NULL;
tx_failed: for (i = 0; i < registered_queues; i++)
release_sub_crq_queue(adapter, allqueues[i], 1);
kfree(allqueues); return -ENOMEM;
}
staticvoid send_request_cap(struct ibmvnic_adapter *adapter, int retry)
{ struct device *dev = &adapter->vdev->dev; union ibmvnic_crq crq; int max_entries; int cap_reqs;
/* We send out 6 or 7 REQUEST_CAPABILITY CRQs below (depending on * the PROMISC flag). Initialize this count upfront. When the tasklet * receives a response to all of these, it will send the next protocol * message (QUERY_IP_OFFLOAD).
*/ if (!(adapter->netdev->flags & IFF_PROMISC) ||
adapter->promisc_supported)
cap_reqs = 7; else
cap_reqs = 6;
if (!retry) { /* Sub-CRQ entries are 32 byte long */ int entries_page = 4 * PAGE_SIZE / (sizeof(u64) * 4);
atomic_set(&adapter->running_cap_crqs, cap_reqs);
if (adapter->min_tx_entries_per_subcrq > entries_page ||
adapter->min_rx_add_entries_per_subcrq > entries_page) {
dev_err(dev, "Fatal, invalid entries per sub-crq\n"); return;
}
if (!adapter->desired.tx_entries)
adapter->desired.tx_entries =
adapter->max_tx_entries_per_subcrq; if (!adapter->desired.rx_entries)
adapter->desired.rx_entries =
adapter->max_rx_add_entries_per_subcrq;
/* Make sure the hypervisor sees the complete request */
dma_wmb();
rc = plpar_hcall_norets(H_SEND_SUB_CRQ_INDIRECT, ua,
cpu_to_be64(remote_handle),
ioba, num_entries);
if (rc)
print_subcrq_error(dev, rc, __func__);
return rc;
}
staticint ibmvnic_send_crq(struct ibmvnic_adapter *adapter, union ibmvnic_crq *crq)
{ unsignedint ua = adapter->vdev->unit_address; struct device *dev = &adapter->vdev->dev;
u64 *u64_crq = (u64 *)crq; int rc;
if (!adapter->crq.active &&
crq->generic.first != IBMVNIC_CRQ_INIT_CMD) {
dev_warn(dev, "Invalid request detected while CRQ is inactive, possible device state change during reset\n"); return -EINVAL;
}
/* Make sure the hypervisor sees the complete request */
dma_wmb();
staticint vnic_client_data_len(struct ibmvnic_adapter *adapter)
{ int len;
/* Calculate the amount of buffer space needed for the * vnic client data in the login buffer. There are four entries, * OS name, LPAR name, device name, and a null last entry.
*/
len = 4 * sizeof(struct vnic_login_client_data);
len += 6; /* "Linux" plus NULL */
len += strlen(utsname()->nodename) + 1;
len += strlen(adapter->netdev->name) + 1;
for (size_t i = 0; i < len; i += 16) {
hex_dump_to_buffer((unsignedchar *)buf + i, len - i, 16, 8,
hex_str, sizeof(hex_str), false);
netdev_dbg(dev, "%s\n", hex_str);
}
}
staticint send_login(struct ibmvnic_adapter *adapter)
{ struct ibmvnic_login_rsp_buffer *login_rsp_buffer; struct ibmvnic_login_buffer *login_buffer; struct device *dev = &adapter->vdev->dev; struct vnic_login_client_data *vlcd;
dma_addr_t rsp_buffer_token;
dma_addr_t buffer_token;
size_t rsp_buffer_size; union ibmvnic_crq crq; int client_data_len;
size_t buffer_size;
__be64 *tx_list_p;
__be64 *rx_list_p; int rc; int i;
if (!adapter->tx_scrq || !adapter->rx_scrq) {
netdev_err(adapter->netdev, "RX or TX queues are not allocated, device login failed\n"); return -ENOMEM;
}
/* Send a series of CRQs requesting various capabilities of the VNIC server */ staticvoid send_query_cap(struct ibmvnic_adapter *adapter)
{ union ibmvnic_crq crq; int cap_reqs;
/* We send out 25 QUERY_CAPABILITY CRQs below. Initialize this count * upfront. When the tasklet receives a response to all of these, it * can send out the next protocol messaage (REQUEST_CAPABILITY).
*/
cap_reqs = 25;
if (crq->get_vpd_rsp.rc.code) {
dev_err(dev, "Error retrieving VPD from device, rc=%x\n",
crq->get_vpd_rsp.rc.code); goto complete;
}
/* get the position of the firmware version info * located after the ASCII 'RM' substring in the buffer
*/
substr = strnstr(adapter->vpd->buff, "RM", adapter->vpd->len); if (!substr) {
dev_info(dev, "Warning - No FW level has been provided in the VPD buffer by the VIOS Server\n"); goto complete;
}
/* get length of firmware level ASCII substring */ if ((substr + 2) < (adapter->vpd->buff + adapter->vpd->len)) {
fw_level_len = *(substr + 2);
} else {
dev_info(dev, "Length of FW substr extrapolated VDP buff\n"); goto complete;
}
/* copy firmware version string from vpd into adapter */ if ((substr + 3 + fw_level_len) <
(adapter->vpd->buff + adapter->vpd->len)) {
strscpy(adapter->fw_version, substr + 3, sizeof(adapter->fw_version));
} else {
dev_info(dev, "FW substr extrapolated VPD buff\n");
}
rc = crq->change_mac_addr_rsp.rc.code; if (rc) {
dev_err(dev, "Error %ld in CHANGE_MAC_ADDR_RSP\n", rc); goto out;
} /* crq->change_mac_addr.mac_addr is the requested one * crq->change_mac_addr_rsp.mac_addr is the returned valid one.
*/
eth_hw_addr_set(netdev, &crq->change_mac_addr_rsp.mac_addr[0]);
ether_addr_copy(adapter->mac_addr,
&crq->change_mac_addr_rsp.mac_addr[0]);
out:
complete(&adapter->fw_done); return rc;
}
/* CHECK: Test/set of login_pending does not need to be atomic * because only ibmvnic_tasklet tests/clears this.
*/ if (!adapter->login_pending) {
netdev_warn(netdev, "Ignoring unexpected login response\n"); return 0;
}
adapter->login_pending = false;
/* If the number of queues requested can't be allocated by the * server, the login response will return with code 1. We will need * to resend the login buffer with fewer queues requested.
*/ if (login_rsp_crq->generic.rc.code) {
adapter->init_done_rc = login_rsp_crq->generic.rc.code;
complete(&adapter->init_done); return 0;
}
if (adapter->failover_pending) {
adapter->init_done_rc = -EAGAIN;
netdev_dbg(netdev, "Failover pending, ignoring login response\n");
complete(&adapter->init_done); /* login response buffer will be released on reset */ return 0;
}
rsp_len = be32_to_cpu(login_rsp->len); if (be32_to_cpu(login->login_rsp_len) < rsp_len ||
rsp_len <= be32_to_cpu(login_rsp->off_txsubm_subcrqs) ||
rsp_len <= be32_to_cpu(login_rsp->off_rxadd_subcrqs) ||
rsp_len <= be32_to_cpu(login_rsp->off_rxadd_buff_size) ||
rsp_len <= be32_to_cpu(login_rsp->off_supp_tx_desc)) { /* This can happen if a login request times out and there are * 2 outstanding login requests sent, the LOGIN_RSP crq * could have been for the older login request. So we are * parsing the newer response buffer which may be incomplete
*/
dev_err(dev, "FATAL: Login rsp offsets/lengths invalid\n");
ibmvnic_reset(adapter, VNIC_RESET_FATAL); return -EIO;
}
size_array = (u64 *)((u8 *)(adapter->login_rsp_buf) +
be32_to_cpu(adapter->login_rsp_buf->off_rxadd_buff_size)); /* variable buffer sizes are not supported, so just read the * first entry.
*/
adapter->cur_rx_buf_sz = be64_to_cpu(size_array[0]);
netdev_dbg(netdev, "Handling CRQ: %016lx %016lx\n",
(unsignedlong)cpu_to_be64(u64_crq[0]),
(unsignedlong)cpu_to_be64(u64_crq[1])); switch (gen_crq->first) { case IBMVNIC_CRQ_INIT_RSP: switch (gen_crq->cmd) { case IBMVNIC_CRQ_INIT:
dev_info(dev, "Partner initialized\n");
adapter->from_passive_init = true; /* Discard any stale login responses from prev reset. * CHECK: should we clear even on INIT_COMPLETE?
*/
adapter->login_pending = false;
if (rc && rc != -EBUSY) { /* We were unable to schedule the failover * reset either because the adapter was still * probing (eg: during kexec) or we could not * allocate memory. Clear the failover_pending * flag since no one else will. We ignore * EBUSY because it means either FAILOVER reset * is already scheduled or the adapter is * being removed.
*/
netdev_err(netdev, "Error %ld scheduling failover reset\n",
rc);
adapter->failover_pending = false;
}
if (!completion_done(&adapter->init_done)) { if (!adapter->init_done_rc)
adapter->init_done_rc = -EAGAIN;
complete(&adapter->init_done);
}
break; case IBMVNIC_CRQ_INIT_COMPLETE:
dev_info(dev, "Partner initialization complete\n");
adapter->crq.active = true;
send_version_xchg(adapter); break; default:
dev_err(dev, "Unknown crq cmd: %d\n", gen_crq->cmd);
} return; case IBMVNIC_CRQ_XPORT_EVENT:
netif_carrier_off(netdev);
adapter->crq.active = false; /* terminate any thread waiting for a response * from the device
*/ if (!completion_done(&adapter->fw_done)) {
adapter->fw_done_rc = -EIO;
complete(&adapter->fw_done);
}
/* if we got here during crq-init, retry crq-init */ if (!completion_done(&adapter->init_done)) {
adapter->init_done_rc = -EAGAIN;
complete(&adapter->init_done);
}
if (!completion_done(&adapter->stats_done))
complete(&adapter->stats_done); if (test_bit(0, &adapter->resetting))
adapter->force_reset_recovery = true; if (gen_crq->cmd == IBMVNIC_PARTITION_MIGRATED) {
dev_info(dev, "Migrated, re-enabling adapter\n");
ibmvnic_reset(adapter, VNIC_RESET_MOBILITY);
} elseif (gen_crq->cmd == IBMVNIC_DEVICE_FAILOVER) {
dev_info(dev, "Backing device failover detected\n");
adapter->failover_pending = true;
} else { /* The adapter lost the connection */
dev_err(dev, "Virtual Adapter failed (rc=%d)\n",
gen_crq->cmd);
ibmvnic_reset(adapter, VNIC_RESET_FATAL);
} return; case IBMVNIC_CRQ_CMD_RSP: break; default:
dev_err(dev, "Got an invalid msg type 0x%02x\n",
gen_crq->first); return;
}
switch (gen_crq->cmd) { case VERSION_EXCHANGE_RSP:
rc = crq->version_exchange_rsp.rc.code; if (rc) {
dev_err(dev, "Error %ld in VERSION_EXCHG_RSP\n", rc); break;
}
ibmvnic_version =
be16_to_cpu(crq->version_exchange_rsp.version);
dev_info(dev, "Partner protocol version is %d\n",
ibmvnic_version);
send_query_cap(adapter); break; case QUERY_CAPABILITY_RSP:
handle_query_cap_rsp(crq, adapter); break; case QUERY_MAP_RSP:
handle_query_map_rsp(crq, adapter); break; case REQUEST_MAP_RSP:
adapter->fw_done_rc = crq->request_map_rsp.rc.code;
complete(&adapter->fw_done); break; case REQUEST_UNMAP_RSP:
handle_request_unmap_rsp(crq, adapter); break; case REQUEST_CAPABILITY_RSP:
handle_request_cap_rsp(crq, adapter); break; case LOGIN_RSP:
netdev_dbg(netdev, "Got Login Response\n");
handle_login_rsp(crq, adapter); break; case LOGICAL_LINK_STATE_RSP:
netdev_dbg(netdev, "Got Logical Link State Response, state: %d rc: %d\n",
crq->logical_link_state_rsp.link_state,
crq->logical_link_state_rsp.rc.code);
adapter->logical_link_state =
crq->logical_link_state_rsp.link_state;
adapter->init_done_rc = crq->logical_link_state_rsp.rc.code;
complete(&adapter->init_done); break; case LINK_STATE_INDICATION:
netdev_dbg(netdev, "Got Logical Link State Indication\n");
adapter->phys_link_state =
crq->link_state_indication.phys_link_state;
adapter->logical_link_state =
crq->link_state_indication.logical_link_state; if (adapter->phys_link_state && adapter->logical_link_state)
netif_carrier_on(netdev); else
netif_carrier_off(netdev); break; case CHANGE_MAC_ADDR_RSP:
netdev_dbg(netdev, "Got MAC address change Response\n");
adapter->fw_done_rc = handle_change_mac_rsp(crq, adapter); break; case ERROR_INDICATION:
netdev_dbg(netdev, "Got Error Indication\n");
handle_error_indication(crq, adapter); break; case REQUEST_STATISTICS_RSP:
netdev_dbg(netdev, "Got Statistics Response\n");
complete(&adapter->stats_done); break; case QUERY_IP_OFFLOAD_RSP:
netdev_dbg(netdev, "Got Query IP offload Response\n");
handle_query_ip_offload_rsp(adapter); break; case MULTICAST_CTRL_RSP:
netdev_dbg(netdev, "Got multicast control Response\n"); break; case CONTROL_IP_OFFLOAD_RSP:
netdev_dbg(netdev, "Got Control IP offload Response\n");
dma_unmap_single(dev, adapter->ip_offload_ctrl_tok, sizeof(adapter->ip_offload_ctrl),
DMA_TO_DEVICE);
complete(&adapter->init_done); break; case COLLECT_FW_TRACE_RSP:
netdev_dbg(netdev, "Got Collect firmware trace Response\n");
complete(&adapter->fw_done); break; case GET_VPD_SIZE_RSP:
handle_vpd_size_rsp(crq, adapter); break; case GET_VPD_RSP:
handle_vpd_rsp(crq, adapter); break; case QUERY_PHYS_PARMS_RSP:
adapter->fw_done_rc = handle_query_phys_parms_rsp(crq, adapter);
complete(&adapter->fw_done); break; default:
netdev_err(netdev, "Got an invalid cmd type 0x%02x\n",
gen_crq->cmd);
}
}
/* Pull all the valid messages off the CRQ */ while ((crq = ibmvnic_next_crq(adapter)) != NULL) { /* This barrier makes sure ibmvnic_next_crq()'s * crq->generic.first & IBMVNIC_CRQ_CMD_RSP is loaded * before ibmvnic_handle_crq()'s * switch(gen_crq->first) and switch(gen_crq->cmd).
*/
dma_rmb();
ibmvnic_handle_crq(crq, adapter);
crq->generic.first = 0;
}
/* And re-open it again */
rc = plpar_hcall_norets(H_REG_CRQ, vdev->unit_address,
crq->msg_token, PAGE_SIZE);
if (rc == H_CLOSED) /* Adapter is good, but other end is not ready */
dev_warn(dev, "Partner adapter not ready\n"); elseif (rc != 0)
dev_warn(dev, "Couldn't register crq (rc=%d)\n", rc);
if (reset &&
test_bit(0, &adapter->resetting) && !adapter->wait_for_reset &&
adapter->reset_reason != VNIC_RESET_MOBILITY) { if (adapter->req_rx_queues != old_num_rx_queues ||
adapter->req_tx_queues != old_num_tx_queues) {
release_sub_crqs(adapter, 0);
rc = init_sub_crqs(adapter);
} else { /* no need to reinitialize completely, but we do * need to clean up transmits that were in flight * when we processed the reset. Failure to do so * will confound the upper layer, usually TCP, by * creating the illusion of transmits that are * awaiting completion.
*/
clean_tx_pools(adapter);
init_success = false; do {
reinit_init_done(adapter);
/* clear any failovers we got in the previous pass * since we are reinitializing the CRQ
*/
adapter->failover_pending = false;
/* If we had already initialized CRQ, we may have one or * more resets queued already. Discard those and release * the CRQ before initializing the CRQ again.
*/
release_crq_queue(adapter);
/* Since we are still in PROBING state, __ibmvnic_reset() * will not access the ->rwi_list and since we released CRQ, * we won't get _new_ transport events. But there maybe an * ongoing ibmvnic_reset() call. So serialize access to * rwi_list. If we win the race, ibvmnic_reset() could add * a reset after we purged but thats ok - we just may end * up with an extra reset (i.e similar to having two or more * resets in the queue at once). * CHECK.
*/
spin_lock_irqsave(&adapter->rwi_lock, flags);
flush_reset_queue(adapter);
spin_unlock_irqrestore(&adapter->rwi_lock, flags);
rc = ibmvnic_reset_init(adapter, false);
} while (rc == -EAGAIN);
/* We are ignoring the error from ibmvnic_reset_init() assuming that the * partner is not ready. CRQ is not active. When the partner becomes * ready, we will do the passive init reset.
*/
if (!rc)
init_success = true;
rc = init_stats_buffers(adapter); if (rc) goto ibmvnic_init_fail;
rc = init_stats_token(adapter); if (rc) goto ibmvnic_stats_fail;
rc = device_create_file(&dev->dev, &dev_attr_failover); if (rc) goto ibmvnic_dev_file_err;
/* cleanup worker thread after releasing CRQ so we don't get * transport events (i.e new work items for the worker thread).
*/
adapter->state = VNIC_REMOVING;
complete(&adapter->probe_done);
flush_work(&adapter->ibmvnic_reset);
flush_delayed_work(&adapter->ibmvnic_delayed_reset);
/* If ibmvnic_reset() is scheduling a reset, wait for it to * finish. Then, set the state to REMOVING to prevent it from * scheduling any more work and to have reset functions ignore * any resets that have already been scheduled. Drop the lock * after setting state, so __ibmvnic_reset() which is called * from the flush_work() below, can make progress.
*/
spin_lock(&adapter->rwi_lock);
adapter->state = VNIC_REMOVING;
spin_unlock(&adapter->rwi_lock);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.