| // 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. */ |
| /* */ |
| /**************************************************************************/ |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/types.h> |
| #include <linux/errno.h> |
| #include <linux/completion.h> |
| #include <linux/ioport.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/kernel.h> |
| #include <linux/netdevice.h> |
| #include <linux/etherdevice.h> |
| #include <linux/skbuff.h> |
| #include <linux/init.h> |
| #include <linux/delay.h> |
| #include <linux/mm.h> |
| #include <linux/ethtool.h> |
| #include <linux/proc_fs.h> |
| #include <linux/if_arp.h> |
| #include <linux/in.h> |
| #include <linux/ip.h> |
| #include <linux/ipv6.h> |
| #include <linux/irq.h> |
| #include <linux/kthread.h> |
| #include <linux/seq_file.h> |
| #include <linux/interrupt.h> |
| #include <net/net_namespace.h> |
| #include <asm/hvcall.h> |
| #include <linux/atomic.h> |
| #include <asm/vio.h> |
| #include <asm/iommu.h> |
| #include <linux/uaccess.h> |
| #include <asm/firmware.h> |
| #include <linux/workqueue.h> |
| #include <linux/if_vlan.h> |
| #include <linux/utsname.h> |
| |
| #include "ibmvnic.h" |
| |
| static const char ibmvnic_driver_name[] = "ibmvnic"; |
| static const char 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); |
| |
| static int ibmvnic_version = IBMVNIC_INITIAL_VERSION; |
| static int ibmvnic_remove(struct vio_dev *); |
| static void release_sub_crqs(struct ibmvnic_adapter *, bool); |
| static int ibmvnic_reset_crq(struct ibmvnic_adapter *); |
| static int ibmvnic_send_crq_init(struct ibmvnic_adapter *); |
| static int ibmvnic_reenable_crq_queue(struct ibmvnic_adapter *); |
| static int ibmvnic_send_crq(struct ibmvnic_adapter *, union ibmvnic_crq *); |
| static int send_subcrq(struct ibmvnic_adapter *adapter, u64 remote_handle, |
| union sub_crq *sub_crq); |
| static int send_subcrq_indirect(struct ibmvnic_adapter *, u64, u64, u64); |
| static irqreturn_t ibmvnic_interrupt_rx(int irq, void *instance); |
| static int enable_scrq_irq(struct ibmvnic_adapter *, |
| struct ibmvnic_sub_crq_queue *); |
| static int disable_scrq_irq(struct ibmvnic_adapter *, |
| struct ibmvnic_sub_crq_queue *); |
| static int pending_scrq(struct ibmvnic_adapter *, |
| struct ibmvnic_sub_crq_queue *); |
| static union sub_crq *ibmvnic_next_scrq(struct ibmvnic_adapter *, |
| struct ibmvnic_sub_crq_queue *); |
| static int ibmvnic_poll(struct napi_struct *napi, int data); |
| static void send_map_query(struct ibmvnic_adapter *adapter); |
| static int send_request_map(struct ibmvnic_adapter *, dma_addr_t, __be32, u8); |
| static int send_request_unmap(struct ibmvnic_adapter *, u8); |
| static int send_login(struct ibmvnic_adapter *adapter); |
| static void send_cap_queries(struct ibmvnic_adapter *adapter); |
| static int init_sub_crqs(struct ibmvnic_adapter *); |
| static int init_sub_crq_irqs(struct ibmvnic_adapter *adapter); |
| static int ibmvnic_init(struct ibmvnic_adapter *); |
| static int ibmvnic_reset_init(struct ibmvnic_adapter *); |
| static void release_crq_queue(struct ibmvnic_adapter *); |
| static int __ibmvnic_set_mac(struct net_device *, u8 *); |
| static int init_crq_queue(struct ibmvnic_adapter *adapter); |
| static int send_query_phys_parms(struct ibmvnic_adapter *adapter); |
| |
| struct ibmvnic_stat { |
| char name[ETH_GSTRING_LEN]; |
| int offset; |
| }; |
| |
| #define IBMVNIC_STAT_OFF(stat) (offsetof(struct ibmvnic_adapter, stats) + \ |
| offsetof(struct ibmvnic_statistics, stat)) |
| #define IBMVNIC_GET_STAT(a, off) (*((u64 *)(((unsigned long)(a)) + off))) |
| |
| static const struct ibmvnic_stat ibmvnic_stats[] = { |
| {"rx_packets", IBMVNIC_STAT_OFF(rx_packets)}, |
| {"rx_bytes", IBMVNIC_STAT_OFF(rx_bytes)}, |
| {"tx_packets", IBMVNIC_STAT_OFF(tx_packets)}, |
| {"tx_bytes", IBMVNIC_STAT_OFF(tx_bytes)}, |
| {"ucast_tx_packets", IBMVNIC_STAT_OFF(ucast_tx_packets)}, |
| {"ucast_rx_packets", IBMVNIC_STAT_OFF(ucast_rx_packets)}, |
| {"mcast_tx_packets", IBMVNIC_STAT_OFF(mcast_tx_packets)}, |
| {"mcast_rx_packets", IBMVNIC_STAT_OFF(mcast_rx_packets)}, |
| {"bcast_tx_packets", IBMVNIC_STAT_OFF(bcast_tx_packets)}, |
| {"bcast_rx_packets", IBMVNIC_STAT_OFF(bcast_rx_packets)}, |
| {"align_errors", IBMVNIC_STAT_OFF(align_errors)}, |
| {"fcs_errors", IBMVNIC_STAT_OFF(fcs_errors)}, |
| {"single_collision_frames", IBMVNIC_STAT_OFF(single_collision_frames)}, |
| {"multi_collision_frames", IBMVNIC_STAT_OFF(multi_collision_frames)}, |
| {"sqe_test_errors", IBMVNIC_STAT_OFF(sqe_test_errors)}, |
| {"deferred_tx", IBMVNIC_STAT_OFF(deferred_tx)}, |
| {"late_collisions", IBMVNIC_STAT_OFF(late_collisions)}, |
| {"excess_collisions", IBMVNIC_STAT_OFF(excess_collisions)}, |
| {"internal_mac_tx_errors", IBMVNIC_STAT_OFF(internal_mac_tx_errors)}, |
| {"carrier_sense", IBMVNIC_STAT_OFF(carrier_sense)}, |
| {"too_long_frames", IBMVNIC_STAT_OFF(too_long_frames)}, |
| {"internal_mac_rx_errors", IBMVNIC_STAT_OFF(internal_mac_rx_errors)}, |
| }; |
| |
| static long h_reg_sub_crq(unsigned long unit_address, unsigned long token, |
| unsigned long length, unsigned long *number, |
| unsigned long *irq) |
| { |
| unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; |
| long rc; |
| |
| rc = plpar_hcall(H_REG_SUB_CRQ, retbuf, unit_address, token, length); |
| *number = retbuf[0]; |
| *irq = retbuf[1]; |
| |
| return rc; |
| } |
| |
| /** |
| * 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. |
| */ |
| static int ibmvnic_wait_for_completion(struct ibmvnic_adapter *adapter, |
| struct completion *comp_done, |
| unsigned long timeout) |
| { |
| struct net_device *netdev; |
| unsigned long 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; |
| } |
| |
| static int alloc_long_term_buff(struct ibmvnic_adapter *adapter, |
| struct ibmvnic_long_term_buff *ltb, int size) |
| { |
| struct device *dev = &adapter->vdev->dev; |
| int rc; |
| |
| ltb->size = size; |
| ltb->buff = dma_alloc_coherent(dev, ltb->size, <b->addr, |
| GFP_KERNEL); |
| |
| if (!ltb->buff) { |
| dev_err(dev, "Couldn't alloc long term buffer\n"); |
| return -ENOMEM; |
| } |
| ltb->map_id = adapter->map_id; |
| adapter->map_id++; |
| |
| mutex_lock(&adapter->fw_lock); |
| adapter->fw_done_rc = 0; |
| reinit_completion(&adapter->fw_done); |
| rc = send_request_map(adapter, ltb->addr, |
| ltb->size, ltb->map_id); |
| if (rc) { |
| dma_free_coherent(dev, ltb->size, ltb->buff, ltb->addr); |
| mutex_unlock(&adapter->fw_lock); |
| return rc; |
| } |
| |
| rc = ibmvnic_wait_for_completion(adapter, &adapter->fw_done, 10000); |
| if (rc) { |
| dev_err(dev, |
| "Long term map request aborted or timed out,rc = %d\n", |
| rc); |
| dma_free_coherent(dev, ltb->size, ltb->buff, ltb->addr); |
| mutex_unlock(&adapter->fw_lock); |
| return rc; |
| } |
| |
| if (adapter->fw_done_rc) { |
| dev_err(dev, "Couldn't map long term buffer,rc = %d\n", |
| adapter->fw_done_rc); |
| dma_free_coherent(dev, ltb->size, ltb->buff, ltb->addr); |
| mutex_unlock(&adapter->fw_lock); |
| return -1; |
| } |
| mutex_unlock(&adapter->fw_lock); |
| return 0; |
| } |
| |
| static void free_long_term_buff(struct ibmvnic_adapter *adapter, |
| struct ibmvnic_long_term_buff *ltb) |
| { |
| struct device *dev = &adapter->vdev->dev; |
| |
| if (!ltb->buff) |
| return; |
| |
| if (adapter->reset_reason != VNIC_RESET_FAILOVER && |
| adapter->reset_reason != VNIC_RESET_MOBILITY) |
| send_request_unmap(adapter, ltb->map_id); |
| dma_free_coherent(dev, ltb->size, ltb->buff, ltb->addr); |
| } |
| |
| static int reset_long_term_buff(struct ibmvnic_adapter *adapter, |
| struct ibmvnic_long_term_buff *ltb) |
| { |
| struct device *dev = &adapter->vdev->dev; |
| int rc; |
| |
| memset(ltb->buff, 0, ltb->size); |
| |
| mutex_lock(&adapter->fw_lock); |
| adapter->fw_done_rc = 0; |
| |
| reinit_completion(&adapter->fw_done); |
| rc = send_request_map(adapter, ltb->addr, ltb->size, ltb->map_id); |
| if (rc) { |
| mutex_unlock(&adapter->fw_lock); |
| return rc; |
| } |
| |
| rc = ibmvnic_wait_for_completion(adapter, &adapter->fw_done, 10000); |
| if (rc) { |
| dev_info(dev, |
| "Reset failed, long term map request timed out or aborted\n"); |
| mutex_unlock(&adapter->fw_lock); |
| return rc; |
| } |
| |
| if (adapter->fw_done_rc) { |
| dev_info(dev, |
| "Reset failed, attempting to free and reallocate buffer\n"); |
| free_long_term_buff(adapter, ltb); |
| mutex_unlock(&adapter->fw_lock); |
| return alloc_long_term_buff(adapter, ltb, ltb->size); |
| } |
| mutex_unlock(&adapter->fw_lock); |
| return 0; |
| } |
| |
| static void deactivate_rx_pools(struct ibmvnic_adapter *adapter) |
| { |
| int i; |
| |
| for (i = 0; i < be32_to_cpu(adapter->login_rsp_buf->num_rxadd_subcrqs); |
| i++) |
| adapter->rx_pool[i].active = 0; |
| } |
| |
| static void replenish_rx_pool(struct ibmvnic_adapter *adapter, |
| struct ibmvnic_rx_pool *pool) |
| { |
| int count = pool->size - atomic_read(&pool->available); |
| struct device *dev = &adapter->vdev->dev; |
| int buffers_added = 0; |
| unsigned long lpar_rc; |
| union sub_crq sub_crq; |
| struct sk_buff *skb; |
| unsigned int offset; |
| dma_addr_t dma_addr; |
| unsigned char *dst; |
| u64 *handle_array; |
| int shift = 0; |
| int index; |
| int i; |
| |
| if (!pool->active) |
| return; |
| |
| handle_array = (u64 *)((u8 *)(adapter->login_rsp_buf) + |
| be32_to_cpu(adapter->login_rsp_buf-> |
| off_rxadd_subcrqs)); |
| |
| for (i = 0; i < count; ++i) { |
| skb = alloc_skb(pool->buff_size, GFP_ATOMIC); |
| if (!skb) { |
| dev_err(dev, "Couldn't replenish rx buff\n"); |
| adapter->replenish_no_mem++; |
| break; |
| } |
| |
| index = pool->free_map[pool->next_free]; |
| |
| if (pool->rx_buff[index].skb) |
| dev_err(dev, "Inconsistent free_map!\n"); |
| |
| /* Copy the skb to the long term mapped DMA buffer */ |
| offset = index * pool->buff_size; |
| dst = pool->long_term_buff.buff + offset; |
| memset(dst, 0, pool->buff_size); |
| dma_addr = pool->long_term_buff.addr + offset; |
| pool->rx_buff[index].data = dst; |
| |
| pool->free_map[pool->next_free] = IBMVNIC_INVALID_MAP; |
| pool->rx_buff[index].dma = dma_addr; |
| pool->rx_buff[index].skb = skb; |
| pool->rx_buff[index].pool_index = pool->index; |
| pool->rx_buff[index].size = pool->buff_size; |
| |
| 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[index]); |
| sub_crq.rx_add.ioba = cpu_to_be32(dma_addr); |
| sub_crq.rx_add.map_id = pool->long_term_buff.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); |
| |
| lpar_rc = send_subcrq(adapter, handle_array[pool->index], |
| &sub_crq); |
| if (lpar_rc != H_SUCCESS) |
| goto failure; |
| |
| buffers_added++; |
| adapter->replenish_add_buff_success++; |
| pool->next_free = (pool->next_free + 1) % pool->size; |
| } |
| 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"); |
| pool->free_map[pool->next_free] = index; |
| pool->rx_buff[index].skb = NULL; |
| |
| dev_kfree_skb_any(skb); |
| adapter->replenish_add_buff_failure++; |
| atomic_add(buffers_added, &pool->available); |
| |
| 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); |
| } |
| } |
| |
| static void replenish_pools(struct ibmvnic_adapter *adapter) |
| { |
| int i; |
| |
| adapter->replenish_task_cycles++; |
| for (i = 0; i < be32_to_cpu(adapter->login_rsp_buf->num_rxadd_subcrqs); |
| i++) { |
| if (adapter->rx_pool[i].active) |
| replenish_rx_pool(adapter, &adapter->rx_pool[i]); |
| } |
| } |
| |
| static void release_stats_buffers(struct ibmvnic_adapter *adapter) |
| { |
| kfree(adapter->tx_stats_buffers); |
| kfree(adapter->rx_stats_buffers); |
| adapter->tx_stats_buffers = NULL; |
| adapter->rx_stats_buffers = NULL; |
| } |
| |
| static int init_stats_buffers(struct ibmvnic_adapter *adapter) |
| { |
| adapter->tx_stats_buffers = |
| kcalloc(IBMVNIC_MAX_QUEUES, |
| sizeof(struct ibmvnic_tx_queue_stats), |
| GFP_KERNEL); |
| if (!adapter->tx_stats_buffers) |
| return -ENOMEM; |
| |
| adapter->rx_stats_buffers = |
| kcalloc(IBMVNIC_MAX_QUEUES, |
| sizeof(struct ibmvnic_rx_queue_stats), |
| GFP_KERNEL); |
| if (!adapter->rx_stats_buffers) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| static void release_stats_token(struct ibmvnic_adapter *adapter) |
| { |
| struct device *dev = &adapter->vdev->dev; |
| |
| if (!adapter->stats_token) |
| return; |
| |
| dma_unmap_single(dev, adapter->stats_token, |
| sizeof(struct ibmvnic_statistics), |
| DMA_FROM_DEVICE); |
| adapter->stats_token = 0; |
| } |
| |
| static int init_stats_token(struct ibmvnic_adapter *adapter) |
| { |
| struct device *dev = &adapter->vdev->dev; |
| dma_addr_t stok; |
| |
| stok = dma_map_single(dev, &adapter->stats, |
| sizeof(struct ibmvnic_statistics), |
| DMA_FROM_DEVICE); |
| if (dma_mapping_error(dev, stok)) { |
| dev_err(dev, "Couldn't map stats buffer\n"); |
| return -1; |
| } |
| |
| adapter->stats_token = stok; |
| netdev_dbg(adapter->netdev, "Stats token initialized (%llx)\n", stok); |
| return 0; |
| } |
| |
| static int reset_rx_pools(struct ibmvnic_adapter *adapter) |
| { |
| struct ibmvnic_rx_pool *rx_pool; |
| int rx_scrqs; |
| int i, j, rc; |
| u64 *size_array; |
| |
| size_array = (u64 *)((u8 *)(adapter->login_rsp_buf) + |
| be32_to_cpu(adapter->login_rsp_buf->off_rxadd_buff_size)); |
| |
| rx_scrqs = be32_to_cpu(adapter->login_rsp_buf->num_rxadd_subcrqs); |
| for (i = 0; i < rx_scrqs; i++) { |
| rx_pool = &adapter->rx_pool[i]; |
| |
| netdev_dbg(adapter->netdev, "Re-setting rx_pool[%d]\n", i); |
| |
| if (rx_pool->buff_size != be64_to_cpu(size_array[i])) { |
| free_long_term_buff(adapter, &rx_pool->long_term_buff); |
| rx_pool->buff_size = be64_to_cpu(size_array[i]); |
| rc = alloc_long_term_buff(adapter, |
| &rx_pool->long_term_buff, |
| rx_pool->size * |
| rx_pool->buff_size); |
| } else { |
| rc = reset_long_term_buff(adapter, |
| &rx_pool->long_term_buff); |
| } |
| |
| if (rc) |
| return rc; |
| |
| for (j = 0; j < rx_pool->size; j++) |
| rx_pool->free_map[j] = j; |
| |
| memset(rx_pool->rx_buff, 0, |
| rx_pool->size * sizeof(struct ibmvnic_rx_buff)); |
| |
| atomic_set(&rx_pool->available, 0); |
| rx_pool->next_alloc = 0; |
| rx_pool->next_free = 0; |
| rx_pool->active = 1; |
| } |
| |
| return 0; |
| } |
| |
| static void 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]; |
| |
| netdev_dbg(adapter->netdev, "Releasing rx_pool[%d]\n", i); |
| |
| kfree(rx_pool->free_map); |
| free_long_term_buff(adapter, &rx_pool->long_term_buff); |
| |
| if (!rx_pool->rx_buff) |
| continue; |
| |
| for (j = 0; j < rx_pool->size; j++) { |
| if (rx_pool->rx_buff[j].skb) { |
| dev_kfree_skb_any(rx_pool->rx_buff[j].skb); |
| rx_pool->rx_buff[j].skb = NULL; |
| } |
| } |
| |
| kfree(rx_pool->rx_buff); |
| } |
| |
| kfree(adapter->rx_pool); |
| adapter->rx_pool = NULL; |
| adapter->num_active_rx_pools = 0; |
| } |
| |
| static int 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; |
| int rxadd_subcrqs; |
| u64 *size_array; |
| int i, j; |
| |
| rxadd_subcrqs = |
| be32_to_cpu(adapter->login_rsp_buf->num_rxadd_subcrqs); |
| size_array = (u64 *)((u8 *)(adapter->login_rsp_buf) + |
| be32_to_cpu(adapter->login_rsp_buf->off_rxadd_buff_size)); |
| |
| adapter->rx_pool = kcalloc(rxadd_subcrqs, |
| sizeof(struct ibmvnic_rx_pool), |
| GFP_KERNEL); |
| if (!adapter->rx_pool) { |
| dev_err(dev, "Failed to allocate rx pools\n"); |
| return -1; |
| } |
| |
| adapter->num_active_rx_pools = rxadd_subcrqs; |
| |
| for (i = 0; i < rxadd_subcrqs; i++) { |
| rx_pool = &adapter->rx_pool[i]; |
| |
| netdev_dbg(adapter->netdev, |
| "Initializing rx_pool[%d], %lld buffs, %lld bytes each\n", |
| i, adapter->req_rx_add_entries_per_subcrq, |
| be64_to_cpu(size_array[i])); |
| |
| rx_pool->size = adapter->req_rx_add_entries_per_subcrq; |
| rx_pool->index = i; |
| rx_pool->buff_size = be64_to_cpu(size_array[i]); |
| rx_pool->active = 1; |
| |
| rx_pool->free_map = kcalloc(rx_pool->size, sizeof(int), |
| GFP_KERNEL); |
| if (!rx_pool->free_map) { |
| release_rx_pools(adapter); |
| return -1; |
| } |
| |
| rx_pool->rx_buff = kcalloc(rx_pool->size, |
| sizeof(struct ibmvnic_rx_buff), |
| GFP_KERNEL); |
| if (!rx_pool->rx_buff) { |
| dev_err(dev, "Couldn't alloc rx buffers\n"); |
| release_rx_pools(adapter); |
| return -1; |
| } |
| |
| if (alloc_long_term_buff(adapter, &rx_pool->long_term_buff, |
| rx_pool->size * rx_pool->buff_size)) { |
| release_rx_pools(adapter); |
| return -1; |
| } |
| |
| for (j = 0; j < rx_pool->size; ++j) |
| rx_pool->free_map[j] = j; |
| |
| atomic_set(&rx_pool->available, 0); |
| rx_pool->next_alloc = 0; |
| rx_pool->next_free = 0; |
| } |
| |
| return 0; |
| } |
| |
| static int reset_one_tx_pool(struct ibmvnic_adapter *adapter, |
| struct ibmvnic_tx_pool *tx_pool) |
| { |
| int rc, i; |
| |
| rc = reset_long_term_buff(adapter, &tx_pool->long_term_buff); |
| if (rc) |
| return rc; |
| |
| memset(tx_pool->tx_buff, 0, |
| tx_pool->num_buffers * |
| sizeof(struct ibmvnic_tx_buff)); |
| |
| for (i = 0; i < tx_pool->num_buffers; i++) |
| tx_pool->free_map[i] = i; |
| |
| tx_pool->consumer_index = 0; |
| tx_pool->producer_index = 0; |
| |
| return 0; |
| } |
| |
| static int reset_tx_pools(struct ibmvnic_adapter *adapter) |
| { |
| int tx_scrqs; |
| int i, rc; |
| |
| tx_scrqs = be32_to_cpu(adapter->login_rsp_buf->num_txsubm_subcrqs); |
| for (i = 0; i < tx_scrqs; i++) { |
| rc = reset_one_tx_pool(adapter, &adapter->tso_pool[i]); |
| if (rc) |
| return rc; |
| rc = reset_one_tx_pool(adapter, &adapter->tx_pool[i]); |
| if (rc) |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static void release_vpd_data(struct ibmvnic_adapter *adapter) |
| { |
| if (!adapter->vpd) |
| return; |
| |
| kfree(adapter->vpd->buff); |
| kfree(adapter->vpd); |
| |
| adapter->vpd = NULL; |
| } |
| |
| static void release_one_tx_pool(struct ibmvnic_adapter *adapter, |
| struct ibmvnic_tx_pool *tx_pool) |
| { |
| kfree(tx_pool->tx_buff); |
| kfree(tx_pool->free_map); |
| free_long_term_buff(adapter, &tx_pool->long_term_buff); |
| } |
| |
| static void release_tx_pools(struct ibmvnic_adapter *adapter) |
| { |
| int i; |
| |
| 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]); |
| } |
| |
| kfree(adapter->tx_pool); |
| adapter->tx_pool = NULL; |
| kfree(adapter->tso_pool); |
| adapter->tso_pool = NULL; |
| adapter->num_active_tx_pools = 0; |
| } |
| |
| static int init_one_tx_pool(struct net_device *netdev, |
| struct ibmvnic_tx_pool *tx_pool, |
| int num_entries, int buf_size) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| int i; |
| |
| tx_pool->tx_buff = kcalloc(num_entries, |
| sizeof(struct ibmvnic_tx_buff), |
| GFP_KERNEL); |
| if (!tx_pool->tx_buff) |
| return -1; |
| |
| if (alloc_long_term_buff(adapter, &tx_pool->long_term_buff, |
| num_entries * buf_size)) |
| return -1; |
| |
| tx_pool->free_map = kcalloc(num_entries, sizeof(int), GFP_KERNEL); |
| if (!tx_pool->free_map) |
| return -1; |
| |
| for (i = 0; i < num_entries; i++) |
| tx_pool->free_map[i] = i; |
| |
| tx_pool->consumer_index = 0; |
| tx_pool->producer_index = 0; |
| tx_pool->num_buffers = num_entries; |
| tx_pool->buf_size = buf_size; |
| |
| return 0; |
| } |
| |
| static int init_tx_pools(struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| int tx_subcrqs; |
| int i, rc; |
| |
| tx_subcrqs = be32_to_cpu(adapter->login_rsp_buf->num_txsubm_subcrqs); |
| adapter->tx_pool = kcalloc(tx_subcrqs, |
| sizeof(struct ibmvnic_tx_pool), GFP_KERNEL); |
| if (!adapter->tx_pool) |
| return -1; |
| |
| adapter->tso_pool = kcalloc(tx_subcrqs, |
| sizeof(struct ibmvnic_tx_pool), GFP_KERNEL); |
| if (!adapter->tso_pool) |
| return -1; |
| |
| adapter->num_active_tx_pools = tx_subcrqs; |
| |
| for (i = 0; i < tx_subcrqs; i++) { |
| rc = init_one_tx_pool(netdev, &adapter->tx_pool[i], |
| adapter->req_tx_entries_per_subcrq, |
| adapter->req_mtu + VLAN_HLEN); |
| if (rc) { |
| release_tx_pools(adapter); |
| return rc; |
| } |
| |
| rc = init_one_tx_pool(netdev, &adapter->tso_pool[i], |
| IBMVNIC_TSO_BUFS, |
| IBMVNIC_TSO_BUF_SZ); |
| if (rc) { |
| release_tx_pools(adapter); |
| return rc; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void 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; |
| } |
| |
| static void 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; |
| } |
| |
| static int 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, NAPI_POLL_WEIGHT); |
| } |
| |
| adapter->num_active_rx_napi = adapter->req_rx_queues; |
| return 0; |
| } |
| |
| static void release_napi(struct ibmvnic_adapter *adapter) |
| { |
| int i; |
| |
| if (!adapter->napi) |
| return; |
| |
| for (i = 0; i < adapter->num_active_rx_napi; i++) { |
| netdev_dbg(adapter->netdev, "Releasing napi[%d]\n", i); |
| netif_napi_del(&adapter->napi[i]); |
| } |
| |
| kfree(adapter->napi); |
| adapter->napi = NULL; |
| adapter->num_active_rx_napi = 0; |
| adapter->napi_enabled = false; |
| } |
| |
| static int ibmvnic_login(struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| unsigned long timeout = msecs_to_jiffies(30000); |
| int retry_count = 0; |
| bool retry; |
| int rc; |
| |
| do { |
| retry = false; |
| if (retry_count > IBMVNIC_MAX_QUEUES) { |
| netdev_warn(netdev, "Login attempts exceeded\n"); |
| return -1; |
| } |
| |
| adapter->init_done_rc = 0; |
| reinit_completion(&adapter->init_done); |
| rc = send_login(adapter); |
| if (rc) { |
| netdev_warn(netdev, "Unable to login\n"); |
| return rc; |
| } |
| |
| if (!wait_for_completion_timeout(&adapter->init_done, |
| timeout)) { |
| netdev_warn(netdev, "Login timed out\n"); |
| return -1; |
| } |
| |
| if (adapter->init_done_rc == PARTIALSUCCESS) { |
| retry_count++; |
| release_sub_crqs(adapter, 1); |
| |
| retry = true; |
| netdev_dbg(netdev, |
| "Received partial success, retrying...\n"); |
| adapter->init_done_rc = 0; |
| reinit_completion(&adapter->init_done); |
| send_cap_queries(adapter); |
| if (!wait_for_completion_timeout(&adapter->init_done, |
| timeout)) { |
| netdev_warn(netdev, |
| "Capabilities query timed out\n"); |
| return -1; |
| } |
| |
| rc = init_sub_crqs(adapter); |
| if (rc) { |
| netdev_warn(netdev, |
| "SCRQ initialization failed\n"); |
| return -1; |
| } |
| |
| rc = init_sub_crq_irqs(adapter); |
| if (rc) { |
| netdev_warn(netdev, |
| "SCRQ irq initialization failed\n"); |
| return -1; |
| } |
| } else if (adapter->init_done_rc) { |
| netdev_warn(netdev, "Adapter login failed\n"); |
| return -1; |
| } |
| } while (retry); |
| |
| __ibmvnic_set_mac(netdev, adapter->mac_addr); |
| |
| return 0; |
| } |
| |
| static void release_login_buffer(struct ibmvnic_adapter *adapter) |
| { |
| kfree(adapter->login_buf); |
| adapter->login_buf = NULL; |
| } |
| |
| static void release_login_rsp_buffer(struct ibmvnic_adapter *adapter) |
| { |
| kfree(adapter->login_rsp_buf); |
| adapter->login_rsp_buf = NULL; |
| } |
| |
| static void release_resources(struct ibmvnic_adapter *adapter) |
| { |
| release_vpd_data(adapter); |
| |
| release_tx_pools(adapter); |
| release_rx_pools(adapter); |
| |
| release_napi(adapter); |
| release_login_rsp_buffer(adapter); |
| } |
| |
| static int set_link_state(struct ibmvnic_adapter *adapter, u8 link_state) |
| { |
| struct net_device *netdev = adapter->netdev; |
| unsigned long timeout = msecs_to_jiffies(30000); |
| union ibmvnic_crq crq; |
| bool resend; |
| int rc; |
| |
| netdev_dbg(netdev, "setting link state %d\n", link_state); |
| |
| memset(&crq, 0, sizeof(crq)); |
| crq.logical_link_state.first = IBMVNIC_CRQ_CMD; |
| crq.logical_link_state.cmd = LOGICAL_LINK_STATE; |
| crq.logical_link_state.link_state = link_state; |
| |
| do { |
| resend = false; |
| |
| reinit_completion(&adapter->init_done); |
| rc = ibmvnic_send_crq(adapter, &crq); |
| if (rc) { |
| netdev_err(netdev, "Failed to set link state\n"); |
| return rc; |
| } |
| |
| if (!wait_for_completion_timeout(&adapter->init_done, |
| timeout)) { |
| netdev_err(netdev, "timeout setting link state\n"); |
| return -1; |
| } |
| |
| if (adapter->init_done_rc == 1) { |
| /* Partuial success, delay and re-send */ |
| mdelay(1000); |
| resend = true; |
| } else if (adapter->init_done_rc) { |
| netdev_warn(netdev, "Unable to set link state, rc=%d\n", |
| adapter->init_done_rc); |
| return adapter->init_done_rc; |
| } |
| } while (resend); |
| |
| return 0; |
| } |
| |
| static int set_real_num_queues(struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| int rc; |
| |
| 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; |
| } |
| |
| static int ibmvnic_get_vpd(struct ibmvnic_adapter *adapter) |
| { |
| struct device *dev = &adapter->vdev->dev; |
| union ibmvnic_crq crq; |
| int len = 0; |
| int rc; |
| |
| if (adapter->vpd->buff) |
| len = adapter->vpd->len; |
| |
| mutex_lock(&adapter->fw_lock); |
| adapter->fw_done_rc = 0; |
| reinit_completion(&adapter->fw_done); |
| |
| crq.get_vpd_size.first = IBMVNIC_CRQ_CMD; |
| crq.get_vpd_size.cmd = GET_VPD_SIZE; |
| rc = ibmvnic_send_crq(adapter, &crq); |
| if (rc) { |
| mutex_unlock(&adapter->fw_lock); |
| return rc; |
| } |
| |
| rc = ibmvnic_wait_for_completion(adapter, &adapter->fw_done, 10000); |
| if (rc) { |
| dev_err(dev, "Could not retrieve VPD size, rc = %d\n", rc); |
| mutex_unlock(&adapter->fw_lock); |
| return rc; |
| } |
| mutex_unlock(&adapter->fw_lock); |
| |
| if (!adapter->vpd->len) |
| return -ENODATA; |
| |
| if (!adapter->vpd->buff) |
| adapter->vpd->buff = kzalloc(adapter->vpd->len, GFP_KERNEL); |
| else if (adapter->vpd->len != len) |
| adapter->vpd->buff = |
| krealloc(adapter->vpd->buff, |
| adapter->vpd->len, GFP_KERNEL); |
| |
| if (!adapter->vpd->buff) { |
| dev_err(dev, "Could allocate VPD buffer\n"); |
| return -ENOMEM; |
| } |
| |
| adapter->vpd->dma_addr = |
| dma_map_single(dev, adapter->vpd->buff, adapter->vpd->len, |
| DMA_FROM_DEVICE); |
| if (dma_mapping_error(dev, adapter->vpd->dma_addr)) { |
| dev_err(dev, "Could not map VPD buffer\n"); |
| kfree(adapter->vpd->buff); |
| adapter->vpd->buff = NULL; |
| return -ENOMEM; |
| } |
| |
| mutex_lock(&adapter->fw_lock); |
| adapter->fw_done_rc = 0; |
| reinit_completion(&adapter->fw_done); |
| |
| crq.get_vpd.first = IBMVNIC_CRQ_CMD; |
| crq.get_vpd.cmd = GET_VPD; |
| crq.get_vpd.ioba = cpu_to_be32(adapter->vpd->dma_addr); |
| crq.get_vpd.len = cpu_to_be32((u32)adapter->vpd->len); |
| rc = ibmvnic_send_crq(adapter, &crq); |
| if (rc) { |
| kfree(adapter->vpd->buff); |
| adapter->vpd->buff = NULL; |
| mutex_unlock(&adapter->fw_lock); |
| return rc; |
| } |
| |
| rc = ibmvnic_wait_for_completion(adapter, &adapter->fw_done, 10000); |
| if (rc) { |
| dev_err(dev, "Unable to retrieve VPD, rc = %d\n", rc); |
| kfree(adapter->vpd->buff); |
| adapter->vpd->buff = NULL; |
| mutex_unlock(&adapter->fw_lock); |
| return rc; |
| } |
| |
| mutex_unlock(&adapter->fw_lock); |
| return 0; |
| } |
| |
| static int init_resources(struct ibmvnic_adapter *adapter) |
| { |
| struct net_device *netdev = adapter->netdev; |
| int rc; |
| |
| rc = set_real_num_queues(netdev); |
| if (rc) |
| return rc; |
| |
| adapter->vpd = kzalloc(sizeof(*adapter->vpd), GFP_KERNEL); |
| if (!adapter->vpd) |
| return -ENOMEM; |
| |
| /* Vital Product Data (VPD) */ |
| rc = ibmvnic_get_vpd(adapter); |
| if (rc) { |
| netdev_err(netdev, "failed to initialize Vital Product Data (VPD)\n"); |
| return rc; |
| } |
| |
| adapter->map_id = 1; |
| |
| rc = init_napi(adapter); |
| if (rc) |
| return rc; |
| |
| send_map_query(adapter); |
| |
| rc = init_rx_pools(netdev); |
| if (rc) |
| return rc; |
| |
| rc = init_tx_pools(netdev); |
| return rc; |
| } |
| |
| static int __ibmvnic_open(struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| enum vnic_state prev_state = adapter->state; |
| int i, rc; |
| |
| adapter->state = VNIC_OPENING; |
| replenish_pools(adapter); |
| ibmvnic_napi_enable(adapter); |
| |
| /* 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]); |
| } |
| |
| rc = set_link_state(adapter, IBMVNIC_LOGICAL_LNK_UP); |
| if (rc) { |
| for (i = 0; i < adapter->req_rx_queues; i++) |
| napi_disable(&adapter->napi[i]); |
| release_resources(adapter); |
| return rc; |
| } |
| |
| 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]); |
| } |
| |
| adapter->state = VNIC_OPEN; |
| return rc; |
| } |
| |
| static int ibmvnic_open(struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| int rc; |
| |
| /* 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_OPEN; |
| return 0; |
| } |
| |
| if (adapter->state != VNIC_CLOSED) { |
| rc = ibmvnic_login(netdev); |
| if (rc) |
| return rc; |
| |
| rc = init_resources(adapter); |
| if (rc) { |
| netdev_err(netdev, "failed to initialize resources\n"); |
| release_resources(adapter); |
| return rc; |
| } |
| } |
| |
| rc = __ibmvnic_open(netdev); |
| |
| return rc; |
| } |
| |
| static void 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; |
| |
| if (!adapter->rx_pool) |
| return; |
| |
| rx_scrqs = adapter->num_active_rx_pools; |
| rx_entries = adapter->req_rx_add_entries_per_subcrq; |
| |
| /* 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; |
| |
| netdev_dbg(adapter->netdev, "Cleaning rx_pool[%d]\n", i); |
| for (j = 0; j < rx_entries; j++) { |
| rx_buff = &rx_pool->rx_buff[j]; |
| if (rx_buff && rx_buff->skb) { |
| dev_kfree_skb_any(rx_buff->skb); |
| rx_buff->skb = NULL; |
| } |
| } |
| } |
| } |
| |
| static void clean_one_tx_pool(struct ibmvnic_adapter *adapter, |
| struct ibmvnic_tx_pool *tx_pool) |
| { |
| struct ibmvnic_tx_buff *tx_buff; |
| u64 tx_entries; |
| int i; |
| |
| if (!tx_pool || !tx_pool->tx_buff) |
| return; |
| |
| tx_entries = tx_pool->num_buffers; |
| |
| 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; |
| } |
| } |
| } |
| |
| static void 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]); |
| } |
| } |
| |
| static void ibmvnic_disable_irqs(struct ibmvnic_adapter *adapter) |
| { |
| struct net_device *netdev = adapter->netdev; |
| int 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); |
| } |
| } |
| } |
| } |
| |
| static void ibmvnic_cleanup(struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| |
| /* ensure that transmissions are stopped if called by do_reset */ |
| if (test_bit(0, &adapter->resetting)) |
| netif_tx_disable(netdev); |
| else |
| netif_tx_stop_all_queues(netdev); |
| |
| ibmvnic_napi_disable(adapter); |
| ibmvnic_disable_irqs(adapter); |
| |
| clean_rx_pools(adapter); |
| clean_tx_pools(adapter); |
| } |
| |
| static int __ibmvnic_close(struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| int rc = 0; |
| |
| adapter->state = VNIC_CLOSING; |
| rc = set_link_state(adapter, IBMVNIC_LOGICAL_LNK_DN); |
| if (rc) |
| return rc; |
| adapter->state = VNIC_CLOSED; |
| return 0; |
| } |
| |
| static int ibmvnic_close(struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| int rc; |
| |
| /* 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; |
| } |
| |
| rc = __ibmvnic_close(netdev); |
| ibmvnic_cleanup(netdev); |
| |
| return rc; |
| } |
| |
| /** |
| * build_hdr_data - creates L2/L3/L4 header data buffer |
| * @hdr_field - bitfield determining needed headers |
| * @skb - socket buffer |
| * @hdr_len - array of header lengths |
| * @tot_len - total length of data |
| * |
| * 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. |
| */ |
| static int build_hdr_data(u8 hdr_field, struct sk_buff *skb, |
| int *hdr_len, u8 *hdr_data) |
| { |
| int len = 0; |
| u8 *hdr; |
| |
| if (skb_vlan_tagged(skb) && !skb_vlan_tag_present(skb)) |
| hdr_len[0] = sizeof(struct vlan_ethhdr); |
| else |
| hdr_len[0] = sizeof(struct ethhdr); |
| |
| if (skb->protocol == htons(ETH_P_IP)) { |
| hdr_len[1] = ip_hdr(skb)->ihl * 4; |
| if (ip_hdr(skb)->protocol == IPPROTO_TCP) |
| hdr_len[2] = tcp_hdrlen(skb); |
| else if (ip_hdr(skb)->protocol == IPPROTO_UDP) |
| hdr_len[2] = sizeof(struct udphdr); |
| } else if (skb->protocol == htons(ETH_P_IPV6)) { |
| hdr_len[1] = sizeof(struct ipv6hdr); |
| if (ipv6_hdr(skb)->nexthdr == IPPROTO_TCP) |
| hdr_len[2] = tcp_hdrlen(skb); |
| else if (ipv6_hdr(skb)->nexthdr == IPPROTO_UDP) |
| hdr_len[2] = sizeof(struct udphdr); |
| } else if (skb->protocol == htons(ETH_P_ARP)) { |
| hdr_len[1] = arp_hdr_len(skb->dev); |
| hdr_len[2] = 0; |
| } |
| |
| memset(hdr_data, 0, 120); |
| if ((hdr_field >> 6) & 1) { |
| hdr = skb_mac_header(skb); |
| memcpy(hdr_data, hdr, hdr_len[0]); |
| len += hdr_len[0]; |
| } |
| |
| if ((hdr_field >> 5) & 1) { |
| hdr = skb_network_header(skb); |
| memcpy(hdr_data + len, hdr, hdr_len[1]); |
| len += hdr_len[1]; |
| } |
| |
| if ((hdr_field >> 4) & 1) { |
| hdr = skb_transport_header(skb); |
| memcpy(hdr_data + len, hdr, hdr_len[2]); |
| len += hdr_len[2]; |
| } |
| return len; |
| } |
| |
| /** |
| * create_hdr_descs - create header and header extension descriptors |
| * @hdr_field - bitfield determining needed headers |
| * @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 |
| */ |
| |
| static int 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; |
| |
| memset(&hdr_desc, 0, sizeof(hdr_desc)); |
| if (cur != hdr_data) { |
| data = hdr_desc.hdr_ext.data; |
| tmp = tmp_len > 29 ? 29 : tmp_len; |
| hdr_desc.hdr_ext.first = IBMVNIC_CRQ_CMD; |
| hdr_desc.hdr_ext.type = IBMVNIC_HDR_EXT_DESC; |
| hdr_desc.hdr_ext.len = tmp; |
| } else { |
| data = hdr_desc.hdr.data; |
| tmp = tmp_len > 24 ? 24 : tmp_len; |
| hdr_desc.hdr.first = IBMVNIC_CRQ_CMD; |
| hdr_desc.hdr.type = IBMVNIC_HDR_DESC; |
| hdr_desc.hdr.len = tmp; |
| hdr_desc.hdr.l2_len = (u8)hdr_len[0]; |
| hdr_desc.hdr.l3_len = cpu_to_be16((u16)hdr_len[1]); |
| hdr_desc.hdr.l4_len = (u8)hdr_len[2]; |
| hdr_desc.hdr.flag = hdr_field << 1; |
| } |
| memcpy(data, cur, tmp); |
| tmp_len -= tmp; |
| *scrq_arr = hdr_desc; |
| scrq_arr++; |
| num_descs++; |
| } |
| |
| return num_descs; |
| } |
| |
| /** |
| * build_hdr_descs_arr - build a header descriptor array |
| * @skb - socket buffer |
| * @num_entries - number of descriptors to be sent |
| * @subcrq - first TX descriptor |
| * @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. |
| */ |
| |
| static void build_hdr_descs_arr(struct ibmvnic_tx_buff *txbuff, |
| int *num_entries, u8 hdr_field) |
| { |
| int hdr_len[3] = {0, 0, 0}; |
| int tot_len; |
| u8 *hdr_data = txbuff->hdr_data; |
| |
| tot_len = build_hdr_data(hdr_field, txbuff->skb, hdr_len, |
| txbuff->hdr_data); |
| *num_entries += create_hdr_descs(hdr_field, hdr_data, tot_len, hdr_len, |
| txbuff->indir_arr + 1); |
| } |
| |
| static int 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; |
| } |
| |
| static netdev_tx_t ibmvnic_xmit(struct sk_buff *skb, struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| int queue_num = skb_get_queue_mapping(skb); |
| u8 *hdrs = (u8 *)&adapter->tx_rx_desc_req; |
| struct device *dev = &adapter->vdev->dev; |
| struct ibmvnic_tx_buff *tx_buff = NULL; |
| struct ibmvnic_sub_crq_queue *tx_scrq; |
| struct ibmvnic_tx_pool *tx_pool; |
| unsigned int tx_send_failed = 0; |
| unsigned int tx_map_failed = 0; |
| unsigned int tx_dropped = 0; |
| unsigned int tx_packets = 0; |
| unsigned int tx_bytes = 0; |
| dma_addr_t data_dma_addr; |
| struct netdev_queue *txq; |
| unsigned long lpar_rc; |
| union sub_crq tx_crq; |
| unsigned int offset; |
| int num_entries = 1; |
| unsigned char *dst; |
| u64 *handle_array; |
| int index = 0; |
| u8 proto = 0; |
| netdev_tx_t ret = NETDEV_TX_OK; |
| |
| if (test_bit(0, &adapter->resetting)) { |
| if (!netif_subqueue_stopped(netdev, skb)) |
| netif_stop_subqueue(netdev, queue_num); |
| dev_kfree_skb_any(skb); |
| |
| tx_send_failed++; |
| tx_dropped++; |
| ret = NETDEV_TX_OK; |
| goto out; |
| } |
| |
| if (ibmvnic_xmit_workarounds(skb, netdev)) { |
| tx_dropped++; |
| tx_send_failed++; |
| ret = NETDEV_TX_OK; |
| goto out; |
| } |
| if (skb_is_gso(skb)) |
| tx_pool = &adapter->tso_pool[queue_num]; |
| else |
| tx_pool = &adapter->tx_pool[queue_num]; |
| |
| tx_scrq = adapter->tx_scrq[queue_num]; |
| txq = netdev_get_tx_queue(netdev, skb_get_queue_mapping(skb)); |
| handle_array = (u64 *)((u8 *)(adapter->login_rsp_buf) + |
| be32_to_cpu(adapter->login_rsp_buf->off_txsubm_subcrqs)); |
| |
| index = tx_pool->free_map[tx_pool->consumer_index]; |
| |
| if (index == IBMVNIC_INVALID_MAP) { |
| dev_kfree_skb_any(skb); |
| tx_send_failed++; |
| tx_dropped++; |
| ret = NETDEV_TX_OK; |
| goto out; |
| } |
| |
| tx_pool->free_map[tx_pool->consumer_index] = IBMVNIC_INVALID_MAP; |
| |
| offset = index * tx_pool->buf_size; |
| dst = tx_pool->long_term_buff.buff + offset; |
| memset(dst, 0, tx_pool->buf_size); |
| data_dma_addr = tx_pool->long_term_buff.addr + offset; |
| |
| 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]; |
| |
| memcpy(dst + cur, |
| page_address(skb_frag_page(frag)) + |
| skb_frag_off(frag), skb_frag_size(frag)); |
| cur += skb_frag_size(frag); |
| } |
| } else { |
| skb_copy_from_linear_data(skb, dst, skb->len); |
| } |
| |
| tx_pool->consumer_index = |
| (tx_pool->consumer_index + 1) % tx_pool->num_buffers; |
| |
| tx_buff = &tx_pool->tx_buff[index]; |
| tx_buff->skb = skb; |
| tx_buff->data_dma[0] = data_dma_addr; |
| tx_buff->data_len[0] = skb->len; |
| tx_buff->index = index; |
| tx_buff->pool_index = queue_num; |
| tx_buff->last_frag = true; |
| |
| memset(&tx_crq, 0, sizeof(tx_crq)); |
| tx_crq.v1.first = IBMVNIC_CRQ_CMD; |
| tx_crq.v1.type = IBMVNIC_TX_DESC; |
| tx_crq.v1.n_crq_elem = 1; |
| tx_crq.v1.n_sge = 1; |
| tx_crq.v1.flags1 = IBMVNIC_TX_COMP_NEEDED; |
| |
| if (skb_is_gso(skb)) |
| tx_crq.v1.correlator = |
| cpu_to_be32(index | IBMVNIC_TSO_POOL_MASK); |
| else |
| tx_crq.v1.correlator = cpu_to_be32(index); |
| tx_crq.v1.dma_reg = cpu_to_be16(tx_pool->long_term_buff.map_id); |
| tx_crq.v1.sge_len = cpu_to_be32(skb->len); |
| tx_crq.v1.ioba = cpu_to_be64(data_dma_addr); |
| |
| if (adapter->vlan_header_insertion && skb_vlan_tag_present(skb)) { |
| tx_crq.v1.flags2 |= IBMVNIC_TX_VLAN_INSERT; |
| tx_crq.v1.vlan_id = cpu_to_be16(skb->vlan_tci); |
| } |
| |
| if (skb->protocol == htons(ETH_P_IP)) { |
| tx_crq.v1.flags1 |= IBMVNIC_TX_PROT_IPV4; |
| proto = ip_hdr(skb)->protocol; |
| } else if (skb->protocol == htons(ETH_P_IPV6)) { |
| tx_crq.v1.flags1 |= IBMVNIC_TX_PROT_IPV6; |
| proto = ipv6_hdr(skb)->nexthdr; |
| } |
| |
| if (proto == IPPROTO_TCP) |
| tx_crq.v1.flags1 |= IBMVNIC_TX_PROT_TCP; |
| else if (proto == IPPROTO_UDP) |
| tx_crq.v1.flags1 |= IBMVNIC_TX_PROT_UDP; |
| |
| if (skb->ip_summed == CHECKSUM_PARTIAL) { |
| tx_crq.v1.flags1 |= IBMVNIC_TX_CHKSUM_OFFLOAD; |
| hdrs += 2; |
| } |
| if (skb_is_gso(skb)) { |
| tx_crq.v1.flags1 |= IBMVNIC_TX_LSO; |
| tx_crq.v1.mss = cpu_to_be16(skb_shinfo(skb)->gso_size); |
| hdrs += 2; |
| } |
| /* determine if l2/3/4 headers are sent to firmware */ |
| if ((*hdrs >> 7) & 1) { |
| build_hdr_descs_arr(tx_buff, &num_entries, *hdrs); |
| tx_crq.v1.n_crq_elem = num_entries; |
| tx_buff->num_entries = num_entries; |
| tx_buff->indir_arr[0] = tx_crq; |
| tx_buff->indir_dma = dma_map_single(dev, tx_buff->indir_arr, |
| sizeof(tx_buff->indir_arr), |
| DMA_TO_DEVICE); |
| if (dma_mapping_error(dev, tx_buff->indir_dma)) { |
| dev_kfree_skb_any(skb); |
| tx_buff->skb = NULL; |
| if (!firmware_has_feature(FW_FEATURE_CMO)) |
| dev_err(dev, "tx: unable to map descriptor array\n"); |
| tx_map_failed++; |
| tx_dropped++; |
| ret = NETDEV_TX_OK; |
| goto tx_err_out; |
| } |
| lpar_rc = send_subcrq_indirect(adapter, handle_array[queue_num], |
| (u64)tx_buff->indir_dma, |
| (u64)num_entries); |
| dma_unmap_single(dev, tx_buff->indir_dma, |
| sizeof(tx_buff->indir_arr), DMA_TO_DEVICE); |
| } else { |
| tx_buff->num_entries = num_entries; |
| lpar_rc = send_subcrq(adapter, handle_array[queue_num], |
| &tx_crq); |
| } |
| if (lpar_rc != H_SUCCESS) { |
| if (lpar_rc != H_CLOSED && lpar_rc != H_PARAMETER) |
| dev_err_ratelimited(dev, "tx: send failed\n"); |
| dev_kfree_skb_any(skb); |
| tx_buff->skb = NULL; |
| |
| 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); |
| } |
| |
| tx_send_failed++; |
| tx_dropped++; |
| ret = NETDEV_TX_OK; |
| goto tx_err_out; |
| } |
| |
| if (atomic_add_return(num_entries, &tx_scrq->used) |
| >= adapter->req_tx_entries_per_subcrq) { |
| netdev_dbg(netdev, "Stopping queue %d\n", queue_num); |
| netif_stop_subqueue(netdev, queue_num); |
| } |
| |
| tx_packets++; |
| tx_bytes += skb->len; |
| txq->trans_start = jiffies; |
| ret = NETDEV_TX_OK; |
| goto out; |
| |
| tx_err_out: |
| /* roll back consumer index and map array*/ |
| if (tx_pool->consumer_index == 0) |
| tx_pool->consumer_index = |
| tx_pool->num_buffers - 1; |
| else |
| tx_pool->consumer_index--; |
| tx_pool->free_map[tx_pool->consumer_index] = index; |
| out: |
| netdev->stats.tx_dropped += tx_dropped; |
| netdev->stats.tx_bytes += tx_bytes; |
| netdev->stats.tx_packets += tx_packets; |
| adapter->tx_send_failed += tx_send_failed; |
| adapter->tx_map_failed += tx_map_failed; |
| adapter->tx_stats_buffers[queue_num].packets += tx_packets; |
| adapter->tx_stats_buffers[queue_num].bytes += tx_bytes; |
| adapter->tx_stats_buffers[queue_num].dropped_packets += tx_dropped; |
| |
| return ret; |
| } |
| |
| static void ibmvnic_set_multi(struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| struct netdev_hw_addr *ha; |
| union ibmvnic_crq crq; |
| |
| memset(&crq, 0, sizeof(crq)); |
| crq.request_capability.first = IBMVNIC_CRQ_CMD; |
| crq.request_capability.cmd = REQUEST_CAPABILITY; |
| |
| if (netdev->flags & IFF_PROMISC) { |
| if (!adapter->promisc_supported) |
| return; |
| } else { |
| if (netdev->flags & IFF_ALLMULTI) { |
| /* Accept all multicast */ |
| memset(&crq, 0, sizeof(crq)); |
| crq.multicast_ctrl.first = IBMVNIC_CRQ_CMD; |
| crq.multicast_ctrl.cmd = MULTICAST_CTRL; |
| crq.multicast_ctrl.flags = IBMVNIC_ENABLE_ALL; |
| ibmvnic_send_crq(adapter, &crq); |
| } else if (netdev_mc_empty(netdev)) { |
| /* Reject all multicast */ |
| memset(&crq, 0, sizeof(crq)); |
| crq.multicast_ctrl.first = IBMVNIC_CRQ_CMD; |
| crq.multicast_ctrl.cmd = MULTICAST_CTRL; |
| crq.multicast_ctrl.flags = IBMVNIC_DISABLE_ALL; |
| ibmvnic_send_crq(adapter, &crq); |
| } else { |
| /* Accept one or more multicast(s) */ |
| netdev_for_each_mc_addr(ha, netdev) { |
| memset(&crq, 0, sizeof(crq)); |
| crq.multicast_ctrl.first = IBMVNIC_CRQ_CMD; |
| crq.multicast_ctrl.cmd = MULTICAST_CTRL; |
| crq.multicast_ctrl.flags = IBMVNIC_ENABLE_MC; |
| ether_addr_copy(&crq.multicast_ctrl.mac_addr[0], |
| ha->addr); |
| ibmvnic_send_crq(adapter, &crq); |
| } |
| } |
| } |
| } |
| |
| static int __ibmvnic_set_mac(struct net_device *netdev, u8 *dev_addr) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| union ibmvnic_crq crq; |
| int rc; |
| |
| if (!is_valid_ether_addr(dev_addr)) { |
| rc = -EADDRNOTAVAIL; |
| goto err; |
| } |
| |
| memset(&crq, 0, sizeof(crq)); |
| crq.change_mac_addr.first = IBMVNIC_CRQ_CMD; |
| crq.change_mac_addr.cmd = CHANGE_MAC_ADDR; |
| ether_addr_copy(&crq.change_mac_addr.mac_addr[0], dev_addr); |
| |
| mutex_lock(&adapter->fw_lock); |
| adapter->fw_done_rc = 0; |
| reinit_completion(&adapter->fw_done); |
| |
| rc = ibmvnic_send_crq(adapter, &crq); |
| if (rc) { |
| rc = -EIO; |
| mutex_unlock(&adapter->fw_lock); |
| goto err; |
| } |
| |
| rc = ibmvnic_wait_for_completion(adapter, &adapter->fw_done, 10000); |
| /* netdev->dev_addr is changed in handle_change_mac_rsp function */ |
| if (rc || adapter->fw_done_rc) { |
| rc = -EIO; |
| mutex_unlock(&adapter->fw_lock); |
| goto err; |
| } |
| mutex_unlock(&adapter->fw_lock); |
| return 0; |
| err: |
| ether_addr_copy(adapter->mac_addr, netdev->dev_addr); |
| return rc; |
| } |
| |
| static int ibmvnic_set_mac(struct net_device *netdev, void *p) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| struct sockaddr *addr = p; |
| int rc; |
| |
| rc = 0; |
| ether_addr_copy(adapter->mac_addr, addr->sa_data); |
| if (adapter->state != VNIC_PROBED) |
| rc = __ibmvnic_set_mac(netdev, addr->sa_data); |
| |
| return rc; |
| } |
| |
| /** |
| * do_change_param_reset returns zero if we are able to keep processing reset |
| * events, or non-zero if we hit a fatal error and must halt. |
| */ |
| static int do_change_param_reset(struct ibmvnic_adapter *adapter, |
| struct ibmvnic_rwi *rwi, |
| u32 reset_state) |
| { |
| struct net_device *netdev = adapter->netdev; |
| int i, rc; |
| |
| netdev_dbg(adapter->netdev, "Change param resetting driver (%d)\n", |
| rwi->reset_reason); |
| |
| netif_carrier_off(netdev); |
| adapter->reset_reason = rwi->reset_reason; |
| |
| ibmvnic_cleanup(netdev); |
| |
| if (reset_state == VNIC_OPEN) { |
| rc = __ibmvnic_close(netdev); |
| if (rc) |
| return rc; |
| } |
| |
| release_resources(adapter); |
| release_sub_crqs(adapter, 1); |
| release_crq_queue(adapter); |
| |
| adapter->state = VNIC_PROBED; |
| |
| rc = init_crq_queue(adapter); |
| |
| if (rc) { |
| netdev_err(adapter->netdev, |
| "Couldn't initialize crq. rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = ibmvnic_reset_init(adapter); |
| if (rc) |
| return IBMVNIC_INIT_FAILED; |
| |
| /* If the adapter was in PROBE state prior to the reset, |
| * exit here. |
| */ |
| if (reset_state == VNIC_PROBED) |
| return 0; |
| |
| rc = ibmvnic_login(netdev); |
| if (rc) { |
| adapter->state = reset_state; |
| return rc; |
| } |
| |
| rc = init_resources(adapter); |
| if (rc) |
| return rc; |
| |
| ibmvnic_disable_irqs(adapter); |
| |
| adapter->state = VNIC_CLOSED; |
| |
| if (reset_state == VNIC_CLOSED) |
| return 0; |
| |
| rc = __ibmvnic_open(netdev); |
| if (rc) |
| return IBMVNIC_OPEN_FAILED; |
| |
| /* refresh device's multicast list */ |
| ibmvnic_set_multi(netdev); |
| |
| /* kick napi */ |
| for (i = 0; i < adapter->req_rx_queues; i++) |
| napi_schedule(&adapter->napi[i]); |
| |
| return 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. |
| */ |
| static int do_reset(struct ibmvnic_adapter *adapter, |
| struct ibmvnic_rwi *rwi, u32 reset_state) |
| { |
| u64 old_num_rx_queues, old_num_tx_queues; |
| u64 old_num_rx_slots, old_num_tx_slots; |
| struct net_device *netdev = adapter->netdev; |
| int i, rc; |
| |
| netdev_dbg(adapter->netdev, "Re-setting driver (%d)\n", |
| rwi->reset_reason); |
| |
| rtnl_lock(); |
| |
| netif_carrier_off(netdev); |
| adapter->reset_reason = rwi->reset_reason; |
| |
| old_num_rx_queues = adapter->req_rx_queues; |
| old_num_tx_queues = adapter->req_tx_queues; |
| old_num_rx_slots = adapter->req_rx_add_entries_per_subcrq; |
| old_num_tx_slots = adapter->req_tx_entries_per_subcrq; |
| |
| ibmvnic_cleanup(netdev); |
| |
| if (reset_state == VNIC_OPEN && |
| adapter->reset_reason != VNIC_RESET_MOBILITY && |
| adapter->reset_reason != VNIC_RESET_FAILOVER) { |
| adapter->state = VNIC_CLOSING; |
| |
| /* 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_CLOSING) { |
| rc = -1; |
| goto out; |
| } |
| |
| adapter->state = VNIC_CLOSED; |
| } |
| |
| 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; |
| |
| if (adapter->reset_reason == VNIC_RESET_MOBILITY) { |
| rc = ibmvnic_reenable_crq_queue(adapter); |
| release_sub_crqs(adapter, 1); |
| } else { |
| rc = ibmvnic_reset_crq(adapter); |
| if (!rc) |
| rc = vio_enable_interrupts(adapter->vdev); |
| } |
| |
| if (rc) { |
| netdev_err(adapter->netdev, |
| "Couldn't initialize crq. rc=%d\n", rc); |
| goto out; |
| } |
| |
| rc = ibmvnic_reset_init(adapter); |
| if (rc) { |
| rc = IBMVNIC_INIT_FAILED; |
| goto out; |
| } |
| |
| /* If the adapter was in PROBE state prior to the reset, |
| * exit here. |
| */ |
| if (reset_state == VNIC_PROBED) { |
| rc = 0; |
| goto out; |
| } |
| |
| rc = ibmvnic_login(netdev); |
| if (rc) { |
| adapter->state = reset_state; |
| goto out; |
| } |
| |
| if (adapter->req_rx_queues != old_num_rx_queues || |
| adapter->req_tx_queues != old_num_tx_queues || |
| adapter->req_rx_add_entries_per_subcrq != |
| old_num_rx_slots || |
| adapter->req_tx_entries_per_subcrq != |
| old_num_tx_slots) { |
| release_rx_pools(adapter); |
| release_tx_pools(adapter); |
| release_napi(adapter); |
| release_vpd_data(adapter); |
| |
| rc = init_resources(adapter); |
| if (rc) |
| goto out; |
| |
| } else { |
| rc = reset_tx_pools(adapter); |
| if (rc) |
| goto out; |
| |
| rc = reset_rx_pools(adapter); |
| if (rc) |
| goto out; |
| } |
| ibmvnic_disable_irqs(adapter); |
| } |
| adapter->state = VNIC_CLOSED; |
| |
| if (reset_state == VNIC_CLOSED) { |
| rc = 0; |
| goto out; |
| } |
| |
| rc = __ibmvnic_open(netdev); |
| if (rc) { |
| rc = IBMVNIC_OPEN_FAILED; |
| goto out; |
| } |
| |
| /* refresh device's multicast list */ |
| ibmvnic_set_multi(netdev); |
| |
| /* kick napi */ |
| for (i = 0; i < adapter->req_rx_queues; i++) |
| napi_schedule(&adapter->napi[i]); |
| |
| if (adapter->reset_reason != VNIC_RESET_FAILOVER) |
| call_netdevice_notifiers(NETDEV_NOTIFY_PEERS, netdev); |
| |
| rc = 0; |
| |
| out: |
| rtnl_unlock(); |
| |
| return rc; |
| } |
| |
| static int do_hard_reset(struct ibmvnic_adapter *adapter, |
| struct ibmvnic_rwi *rwi, u32 reset_state) |
| { |
| struct net_device *netdev = adapter->netdev; |
| int rc; |
| |
| netdev_dbg(adapter->netdev, "Hard resetting driver (%d)\n", |
| rwi->reset_reason); |
| |
| netif_carrier_off(netdev); |
| adapter->reset_reason = rwi->reset_reason; |
| |
| ibmvnic_cleanup(netdev); |
| release_resources(adapter); |
| release_sub_crqs(adapter, 0); |
| release_crq_queue(adapter); |
| |
| /* remove the closed state so when we call open it appears |
| * we are coming from the probed state. |
| */ |
| adapter->state = VNIC_PROBED; |
| |
| reinit_completion(&adapter->init_done); |
| rc = init_crq_queue(adapter); |
| if (rc) { |
| netdev_err(adapter->netdev, |
| "Couldn't initialize crq. rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = ibmvnic_init(adapter); |
| if (rc) |
| return rc; |
| |
| /* If the adapter was in PROBE state prior to the reset, |
| * exit here. |
| */ |
| if (reset_state == VNIC_PROBED) |
| return 0; |
| |
| rc = ibmvnic_login(netdev); |
| if (rc) { |
| adapter->state = VNIC_PROBED; |
| return 0; |
| } |
| |
| rc = init_resources(adapter); |
| if (rc) |
| return rc; |
| |
| ibmvnic_disable_irqs(adapter); |
| adapter->state = VNIC_CLOSED; |
| |
| if (reset_state == VNIC_CLOSED) |
| return 0; |
| |
| rc = __ibmvnic_open(netdev); |
| if (rc) |
| return IBMVNIC_OPEN_FAILED; |
| |
| return 0; |
| } |
| |
| static struct ibmvnic_rwi *get_next_rwi(struct ibmvnic_adapter *adapter) |
| { |
| struct ibmvnic_rwi *rwi; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&adapter->rwi_lock, flags); |
| |
| if (!list_empty(&adapter->rwi_list)) { |
| rwi = list_first_entry(&adapter->rwi_list, struct ibmvnic_rwi, |
| list); |
| list_del(&rwi->list); |
| } else { |
| rwi = NULL; |
| } |
| |
| spin_unlock_irqrestore(&adapter->rwi_lock, flags); |
| return rwi; |
| } |
| |
| static void free_all_rwi(struct ibmvnic_adapter *adapter) |
| { |
| struct ibmvnic_rwi *rwi; |
| |
| rwi = get_next_rwi(adapter); |
| while (rwi) { |
| kfree(rwi); |
| rwi = get_next_rwi(adapter); |
| } |
| } |
| |
| static void __ibmvnic_reset(struct work_struct *work) |
| { |
| struct ibmvnic_rwi *rwi; |
| struct ibmvnic_adapter *adapter; |
| bool saved_state = false; |
| unsigned long flags; |
| u32 reset_state; |
| int rc = 0; |
| |
| adapter = container_of(work, struct ibmvnic_adapter, ibmvnic_reset); |
| |
| if (test_and_set_bit_lock(0, &adapter->resetting)) { |
| schedule_delayed_work(&adapter->ibmvnic_delayed_reset, |
| IBMVNIC_RESET_DELAY); |
| return; |
| } |
| |
| rwi = get_next_rwi(adapter); |
| while (rwi) { |
| spin_lock_irqsave(&adapter->state_lock, flags); |
| |
| if (adapter->state == VNIC_REMOVING || |
| adapter->state == VNIC_REMOVED) { |
| spin_unlock_irqrestore(&adapter->state_lock, flags); |
| kfree(rwi); |
| rc = EBUSY; |
| break; |
| } |
| |
| if (!saved_state) { |
| reset_state = adapter->state; |
| adapter->state = VNIC_RESETTING; |
| saved_state = true; |
| } |
| spin_unlock_irqrestore(&adapter->state_lock, flags); |
| |
| if (rwi->reset_reason == VNIC_RESET_CHANGE_PARAM) { |
| /* CHANGE_PARAM requestor holds rtnl_lock */ |
| rc = do_change_param_reset(adapter, rwi, reset_state); |
| } else if (adapter->force_reset_recovery) { |
| /* 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(); |
| } |
| } else { |
| rc = do_reset(adapter, rwi, reset_state); |
| } |
| kfree(rwi); |
| if (rc == IBMVNIC_OPEN_FAILED) { |
| if (list_empty(&adapter->rwi_list)) |
| adapter->state = VNIC_CLOSED; |
| else |
| adapter->state = reset_state; |
| rc = 0; |
| } else if (rc && rc != IBMVNIC_INIT_FAILED && |
| !adapter->force_reset_recovery) |
| break; |
| |
| rwi = get_next_rwi(adapter); |
| |
| if (rwi && (rwi->reset_reason == VNIC_RESET_FAILOVER || |
| rwi->reset_reason == VNIC_RESET_MOBILITY)) |
| adapter->force_reset_recovery = true; |
| } |
| |
| if (adapter->wait_for_reset) { |
| adapter->reset_done_rc = rc; |
| complete(&adapter->reset_done); |
| } |
| |
| if (rc) { |
| netdev_dbg(adapter->netdev, "Reset failed\n"); |
| free_all_rwi(adapter); |
| } |
| |
| clear_bit_unlock(0, &adapter->resetting); |
| } |
| |
| static void __ibmvnic_delayed_reset(struct work_struct *work) |
| { |
| struct ibmvnic_adapter *adapter; |
| |
| adapter = container_of(work, struct ibmvnic_adapter, |
| ibmvnic_delayed_reset.work); |
| __ibmvnic_reset(&adapter->ibmvnic_reset); |
| } |
| |
| static int ibmvnic_reset(struct ibmvnic_adapter *adapter, |
| enum ibmvnic_reset_reason reason) |
| { |
| struct list_head *entry, *tmp_entry; |
| struct ibmvnic_rwi *rwi, *tmp; |
| struct net_device *netdev = adapter->netdev; |
| unsigned long flags; |
| int ret; |
| |
| if (adapter->state == VNIC_REMOVING || |
| adapter->state == VNIC_REMOVED || |
| adapter->failover_pending) { |
| ret = EBUSY; |
| netdev_dbg(netdev, "Adapter removing or pending failover, skipping reset\n"); |
| goto err; |
| } |
| |
| if (adapter->state == VNIC_PROBING) { |
| netdev_warn(netdev, "Adapter reset during probe\n"); |
| ret = adapter->init_done_rc = EAGAIN; |
| goto err; |
| } |
| |
| spin_lock_irqsave(&adapter->rwi_lock, flags); |
| |
| list_for_each(entry, &adapter->rwi_list) { |
| tmp = list_entry(entry, struct ibmvnic_rwi, list); |
| if (tmp->reset_reason == reason) { |
| netdev_dbg(netdev, "Skipping matching reset\n"); |
| spin_unlock_irqrestore(&adapter->rwi_lock, flags); |
| ret = EBUSY; |
| goto err; |
| } |
| } |
| |
| rwi = kzalloc(sizeof(*rwi), GFP_ATOMIC); |
| if (!rwi) { |
| spin_unlock_irqrestore(&adapter->rwi_lock, flags); |
| ibmvnic_close(netdev); |
| ret = ENOMEM; |
| goto err; |
| } |
| /* if we just received a transport event, |
| * flush reset queue and process this reset |
| */ |
| if (adapter->force_reset_recovery && !list_empty(&adapter->rwi_list)) { |
| list_for_each_safe(entry, tmp_entry, &adapter->rwi_list) |
| list_del(entry); |
| } |
| rwi->reset_reason = reason; |
| list_add_tail(&rwi->list, &adapter->rwi_list); |
| spin_unlock_irqrestore(&adapter->rwi_lock, flags); |
| netdev_dbg(adapter->netdev, "Scheduling reset (reason %d)\n", reason); |
| schedule_work(&adapter->ibmvnic_reset); |
| |
| return 0; |
| err: |
| return -ret; |
| } |
| |
| static void ibmvnic_tx_timeout(struct net_device *dev, unsigned int txqueue) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(dev); |
| |
| ibmvnic_reset(adapter, VNIC_RESET_TIMEOUT); |
| } |
| |
| static void remove_buff_from_pool(struct ibmvnic_adapter *adapter, |
| struct ibmvnic_rx_buff *rx_buff) |
| { |
| struct ibmvnic_rx_pool *pool = &adapter->rx_pool[rx_buff->pool_index]; |
| |
| rx_buff->skb = NULL; |
| |
| pool->free_map[pool->next_alloc] = (int)(rx_buff - pool->rx_buff); |
| pool->next_alloc = (pool->next_alloc + 1) % pool->size; |
| |
| atomic_dec(&pool->available); |
| } |
| |
| static int ibmvnic_poll(struct napi_struct *napi, int budget) |
| { |
| struct net_device *netdev = napi->dev; |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| int scrq_num = (int)(napi - adapter->napi); |
| int frames_processed = 0; |
| |
| restart_poll: |
| while (frames_processed < budget) { |
| struct sk_buff *skb; |
| struct ibmvnic_rx_buff *rx_buff; |
| union sub_crq *next; |
| u32 length; |
| u16 offset; |
| u8 flags = 0; |
| |
| if (unlikely(test_bit(0, &adapter->resetting) && |
| adapter->reset_reason != VNIC_RESET_NON_FATAL)) { |
| enable_scrq_irq(adapter, adapter->rx_scrq[scrq_num]); |
| napi_complete_done(napi, frames_processed); |
| return frames_processed; |
| } |
| |
| if (!pending_scrq(adapter, adapter->rx_scrq[scrq_num])) |
| break; |
| next = ibmvnic_next_scrq(adapter, adapter->rx_scrq[scrq_num]); |
| rx_buff = |
| (struct ibmvnic_rx_buff *)be64_to_cpu(next-> |
| rx_comp.correlator); |
| /* do error checking */ |
| if (next->rx_comp.rc) { |
| netdev_dbg(netdev, "rx buffer returned with rc %x\n", |
| be16_to_cpu(next->rx_comp.rc)); |
| /* free the entry */ |
| next->rx_comp.first = 0; |
| dev_kfree_skb_any(rx_buff->skb); |
| remove_buff_from_pool(adapter, rx_buff); |
| continue; |
| } else if (!rx_buff->skb) { |
| /* free the entry */ |
| next->rx_comp.first = 0; |
| remove_buff_from_pool(adapter, rx_buff); |
| continue; |
| } |
| |
| length = be32_to_cpu(next->rx_comp.len); |
| offset = be16_to_cpu(next->rx_comp.off_frame_data); |
| flags = next->rx_comp.flags; |
| skb = rx_buff->skb; |
| skb_copy_to_linear_data(skb, rx_buff->data + offset, |
| length); |
| |
| /* 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); |
| |
| skb_put(skb, length); |
| skb->protocol = eth_type_trans(skb, netdev); |
| skb_record_rx_queue(skb, scrq_num); |
| |
| if (flags & IBMVNIC_IP_CHKSUM_GOOD && |
| flags & IBMVNIC_TCP_UDP_CHKSUM_GOOD) { |
| skb->ip_summed = CHECKSUM_UNNECESSARY; |
| } |
| |
| length = skb->len; |
| napi_gro_receive(napi, skb); /* send it up */ |
| netdev->stats.rx_packets++; |
| netdev->stats.rx_bytes += length; |
| adapter->rx_stats_buffers[scrq_num].packets++; |
| adapter->rx_stats_buffers[scrq_num].bytes += length; |
| frames_processed++; |
| } |
| |
| if (adapter->state != VNIC_CLOSING) |
| replenish_rx_pool(adapter, &adapter->rx_pool[scrq_num]); |
| |
| if (frames_processed < budget) { |
| enable_scrq_irq(adapter, adapter->rx_scrq[scrq_num]); |
| napi_complete_done(napi, frames_processed); |
| if (pending_scrq(adapter, adapter->rx_scrq[scrq_num]) && |
| napi_reschedule(napi)) { |
| disable_scrq_irq(adapter, adapter->rx_scrq[scrq_num]); |
| goto restart_poll; |
| } |
| } |
| return frames_processed; |
| } |
| |
| static int wait_for_reset(struct ibmvnic_adapter *adapter) |
| { |
| int rc, ret; |
| |
| adapter->fallback.mtu = adapter->req_mtu; |
| adapter->fallback.rx_queues = adapter->req_rx_queues; |
| adapter->fallback.tx_queues = adapter->req_tx_queues; |
| adapter->fallback.rx_entries = adapter->req_rx_add_entries_per_subcrq; |
| adapter->fallback.tx_entries = adapter->req_tx_entries_per_subcrq; |
| |
| reinit_completion(&adapter->reset_done); |
| adapter->wait_for_reset = true; |
| rc = ibmvnic_reset(adapter, VNIC_RESET_CHANGE_PARAM); |
| |
| if (rc) { |
| ret = rc; |
| goto out; |
| } |
| rc = ibmvnic_wait_for_completion(adapter, &adapter->reset_done, 60000); |
| if (rc) { |
| ret = -ENODEV; |
| goto out; |
| } |
| |
| ret = 0; |
| if (adapter->reset_done_rc) { |
| ret = -EIO; |
| adapter->desired.mtu = adapter->fallback.mtu; |
| adapter->desired.rx_queues = adapter->fallback.rx_queues; |
| adapter->desired.tx_queues = adapter->fallback.tx_queues; |
| adapter->desired.rx_entries = adapter->fallback.rx_entries; |
| adapter->desired.tx_entries = adapter->fallback.tx_entries; |
| |
| reinit_completion(&adapter->reset_done); |
| adapter->wait_for_reset = true; |
| rc = ibmvnic_reset(adapter, VNIC_RESET_CHANGE_PARAM); |
| if (rc) { |
| ret = rc; |
| goto out; |
| } |
| rc = ibmvnic_wait_for_completion(adapter, &adapter->reset_done, |
| 60000); |
| if (rc) { |
| ret = -ENODEV; |
| goto out; |
| } |
| } |
| out: |
| adapter->wait_for_reset = false; |
| |
| return ret; |
| } |
| |
| static int ibmvnic_change_mtu(struct net_device *netdev, int new_mtu) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| |
| adapter->desired.mtu = new_mtu + ETH_HLEN; |
| |
| return wait_for_reset(adapter); |
| } |
| |
| 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; |
| } |
| |
| return features; |
| } |
| |
| static const struct net_device_ops ibmvnic_netdev_ops = { |
| .ndo_open = ibmvnic_open, |
| .ndo_stop = ibmvnic_close, |
| .ndo_start_xmit = ibmvnic_xmit, |
| .ndo_set_rx_mode = ibmvnic_set_multi, |
| .ndo_set_mac_address = ibmvnic_set_mac, |
| .ndo_validate_addr = eth_validate_addr, |
| .ndo_tx_timeout = ibmvnic_tx_timeout, |
| .ndo_change_mtu = ibmvnic_change_mtu, |
| .ndo_features_check = ibmvnic_features_check, |
| }; |
| |
| /* ethtool functions */ |
| |
| static int ibmvnic_get_link_ksettings(struct net_device *netdev, |
| struct ethtool_link_ksettings *cmd) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| int rc; |
| |
| rc = send_query_phys_parms(adapter); |
| if (rc) { |
| adapter->speed = SPEED_UNKNOWN; |
| adapter->duplex = DUPLEX_UNKNOWN; |
| } |
| cmd->base.speed = adapter->speed; |
| cmd->base.duplex = adapter->duplex; |
| cmd->base.port = PORT_FIBRE; |
| cmd->base.phy_address = 0; |
| cmd->base.autoneg = AUTONEG_ENABLE; |
| |
| return 0; |
| } |
| |
| static void ibmvnic_get_drvinfo(struct net_device *netdev, |
| struct ethtool_drvinfo *info) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| |
| strlcpy(info->driver, ibmvnic_driver_name, sizeof(info->driver)); |
| strlcpy(info->version, IBMVNIC_DRIVER_VERSION, sizeof(info->version)); |
| strlcpy(info->fw_version, adapter->fw_version, |
| sizeof(info->fw_version)); |
| } |
| |
| static u32 ibmvnic_get_msglevel(struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| |
| return adapter->msg_enable; |
| } |
| |
| static void ibmvnic_set_msglevel(struct net_device *netdev, u32 data) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| |
| adapter->msg_enable = data; |
| } |
| |
| static u32 ibmvnic_get_link(struct net_device *netdev) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| |
| /* 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; |
| } |
| |
| static void ibmvnic_get_ringparam(struct net_device *netdev, |
| struct ethtool_ringparam *ring) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| |
| if (adapter->priv_flags & IBMVNIC_USE_SERVER_MAXES) { |
| ring->rx_max_pending = adapter->max_rx_add_entries_per_subcrq; |
| ring->tx_max_pending = adapter->max_tx_entries_per_subcrq; |
| } else { |
| ring->rx_max_pending = IBMVNIC_MAX_QUEUE_SZ; |
| ring->tx_max_pending = IBMVNIC_MAX_QUEUE_SZ; |
| } |
| ring->rx_mini_max_pending = 0; |
| ring->rx_jumbo_max_pending = 0; |
| ring->rx_pending = adapter->req_rx_add_entries_per_subcrq; |
| ring->tx_pending = adapter->req_tx_entries_per_subcrq; |
| ring->rx_mini_pending = 0; |
| ring->rx_jumbo_pending = 0; |
| } |
| |
| static int ibmvnic_set_ringparam(struct net_device *netdev, |
| struct ethtool_ringparam *ring) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| int ret; |
| |
| ret = 0; |
| adapter->desired.rx_entries = ring->rx_pending; |
| adapter->desired.tx_entries = ring->tx_pending; |
| |
| ret = wait_for_reset(adapter); |
| |
| if (!ret && |
| (adapter->req_rx_add_entries_per_subcrq != ring->rx_pending || |
| adapter->req_tx_entries_per_subcrq != ring->tx_pending)) |
| netdev_info(netdev, |
| "Could not match full ringsize request. Requested: RX %d, TX %d; Allowed: RX %llu, TX %llu\n", |
| ring->rx_pending, ring->tx_pending, |
| adapter->req_rx_add_entries_per_subcrq, |
| adapter->req_tx_entries_per_subcrq); |
| return ret; |
| } |
| |
| static void ibmvnic_get_channels(struct net_device *netdev, |
| struct ethtool_channels *channels) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| |
| if (adapter->priv_flags & IBMVNIC_USE_SERVER_MAXES) { |
| channels->max_rx = adapter->max_rx_queues; |
| channels->max_tx = adapter->max_tx_queues; |
| } else { |
| channels->max_rx = IBMVNIC_MAX_QUEUES; |
| channels->max_tx = IBMVNIC_MAX_QUEUES; |
| } |
| |
| channels->max_other = 0; |
| channels->max_combined = 0; |
| channels->rx_count = adapter->req_rx_queues; |
| channels->tx_count = adapter->req_tx_queues; |
| channels->other_count = 0; |
| channels->combined_count = 0; |
| } |
| |
| static int ibmvnic_set_channels(struct net_device *netdev, |
| struct ethtool_channels *channels) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(netdev); |
| int ret; |
| |
| ret = 0; |
| adapter->desired.rx_queues = channels->rx_count; |
| adapter->desired.tx_queues = channels->tx_count; |
| |
| ret = wait_for_reset(adapter); |
| |
| if (!ret && |
| (adapter->req_rx_queues != channels->rx_count || |
| adapter->req_tx_queues != channels->tx_count)) |
| netdev_info(netdev, |
| "Could not match full channels request. Requested: RX %d, TX %d; Allowed: RX %llu, TX %llu\n", |
| channels->rx_count, channels->tx_count, |
| adapter->req_rx_queues, adapter->req_tx_queues); |
| return ret; |
| |
| } |
| |
| static void ibmvnic_get_strings(struct net_device *dev, u32 stringset, u8 *data) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(dev); |
| int i; |
| |
| switch (stringset) { |
| case ETH_SS_STATS: |
| for (i = 0; i < ARRAY_SIZE(ibmvnic_stats); |
| i++, data += ETH_GSTRING_LEN) |
| memcpy(data, ibmvnic_stats[i].name, ETH_GSTRING_LEN); |
| |
| for (i = 0; i < adapter->req_tx_queues; i++) { |
| snprintf(data, ETH_GSTRING_LEN, "tx%d_packets", i); |
| data += ETH_GSTRING_LEN; |
| |
| snprintf(data, ETH_GSTRING_LEN, "tx%d_bytes", i); |
| data += ETH_GSTRING_LEN; |
| |
| snprintf(data, ETH_GSTRING_LEN, |
| "tx%d_dropped_packets", i); |
| data += ETH_GSTRING_LEN; |
| } |
| |
| for (i = 0; i < adapter->req_rx_queues; i++) { |
| snprintf(data, ETH_GSTRING_LEN, "rx%d_packets", i); |
| data += ETH_GSTRING_LEN; |
| |
| snprintf(data, ETH_GSTRING_LEN, "rx%d_bytes", i); |
| data += ETH_GSTRING_LEN; |
| |
| snprintf(data, ETH_GSTRING_LEN, "rx%d_interrupts", i); |
| data += ETH_GSTRING_LEN; |
| } |
| break; |
| |
| case ETH_SS_PRIV_FLAGS: |
| for (i = 0; i < ARRAY_SIZE(ibmvnic_priv_flags); i++) |
| strcpy(data + i * ETH_GSTRING_LEN, |
| ibmvnic_priv_flags[i]); |
| break; |
| default: |
| return; |
| } |
| } |
| |
| static int ibmvnic_get_sset_count(struct net_device *dev, int sset) |
| { |
| struct ibmvnic_adapter *adapter = netdev_priv(dev); |
| |
| switch (sset) { |
| case ETH_SS_STATS: |
| return ARRAY_SIZE(ibmvnic_stats) + |
| adapter->req_tx_queues * NUM_TX_STATS + |
| adapter->req_rx_queues * NUM_RX_STATS; |
| case ETH_SS_PRIV_FLAGS: |
| return ARRAY_SIZE(ibmvnic_priv_flags); |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static void ibmvnic_get_ethtool_stats(struct net_device *dev, |
| struct ethtool_stats *stats, u64 *data) |
| { |
| struct ibmvnic_adapter |