/* * Copyright 2014-2017 Cavium, Inc. * The contents of this file are subject to the terms of the Common Development * and Distribution License, v.1, (the "License"). * * You may not use this file except in compliance with the License. * * You can obtain a copy of the License at available * at http://opensource.org/licenses/CDDL-1.0 * * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, Joyent, Inc. */ #include "bnx.h" #include "bnx_mm.h" #include "bnxgld.h" #include "bnxsnd.h" #include "bnxtmr.h" #include "bnxcfg.h" #include "serdes.h" #include "shmem.h" #define MII_REG(_type, _field) (OFFSETOF(_type, _field)/2) ddi_dma_attr_t bnx_std_dma_attrib = { DMA_ATTR_V0, /* dma_attr_version */ 0, /* dma_attr_addr_lo */ 0xffffffffffffffff, /* dma_attr_addr_hi */ 0x0ffffff, /* dma_attr_count_max */ BNX_DMA_ALIGNMENT, /* dma_attr_align */ 0xffffffff, /* dma_attr_burstsizes */ 1, /* dma_attr_minxfer */ 0x00ffffff, /* dma_attr_maxxfer */ 0xffffffff, /* dma_attr_seg */ 1, /* dma_attr_sgllen */ 1, /* dma_attr_granular */ 0, /* dma_attr_flags */ }; static ddi_dma_attr_t bnx_page_dma_attrib = { DMA_ATTR_V0, /* dma_attr_version */ 0, /* dma_attr_addr_lo */ 0xffffffffffffffff, /* dma_attr_addr_hi */ 0x0ffffff, /* dma_attr_count_max */ LM_PAGE_SIZE, /* dma_attr_align */ 0xffffffff, /* dma_attr_burstsizes */ 1, /* dma_attr_minxfer */ 0x00ffffff, /* dma_attr_maxxfer */ 0xffffffff, /* dma_attr_seg */ 1, /* dma_attr_sgllen */ 1, /* dma_attr_granular */ 0, /* dma_attr_flags */ }; /* * Name: mm_wait * * Input: ptr to LM's device structure, * delay value in micro-secs * * Return: None. * * Description: This funtion will be in a busy loop for specified number of * micro-seconds and will return only after the time is elasped. */ void mm_wait(lm_device_t *pdev, u32_t delay_us) { FLUSHPOSTEDWRITES(pdev); drv_usecwait(delay_us * 10); } /* mm_wait */ /* * Name: mm_read_pci * * Input: ptr to LM's device structure, * register offset into config space, * ptr to u32 where the register value is returned * * Return: LM_STATUS_SUCCESS, if successful * LM_STATUS_FAILURE, if BAR register veiw is not mapped * * Description: This routine reads the PCI config space for the given device * by calling pci_config_get32(). */ lm_status_t mm_read_pci(lm_device_t *pdev, u32_t pci_reg, u32_t *reg_value) { um_device_t *udevp = (um_device_t *)pdev; *reg_value = pci_config_get32(udevp->os_param.pci_cfg_handle, (off_t)pci_reg); return (LM_STATUS_SUCCESS); } /* mm_read_pci */ /* * Name: mm_write_pci * * Input: ptr to LM's device structure, * register offset into config space, * u32 value to be written to PCI config register * * Return: LM_STATUS_SUCCESS, if successful * LM_STATUS_FAILURE, if BAR register veiw is not mapped * * Description: This routine writes to PCI config register using DDI call, * pci_config_put32(). */ lm_status_t mm_write_pci(lm_device_t *pdev, u32_t pci_reg, u32_t reg_value) { um_device_t *udevp = (um_device_t *)pdev; pci_config_put32(udevp->os_param.pci_cfg_handle, (off_t)pci_reg, (uint32_t)reg_value); return (LM_STATUS_SUCCESS); } /* mm_write_pci */ /* * Name: mm_map_io_base * * Input: ptr to LM's device structure, * physical address of the BAR reg * (not used in this implementation), * size of the register window * * Return: ptr to mapped virtual memory * * Description: This routine maps the BAR register window and returns the * virtual address in the CPU address scape */ void * mm_map_io_base(lm_device_t *pdev, lm_address_t base_addr, u32_t size) { um_device_t *udevp = (um_device_t *)pdev; pdev->vars.dmaRegAccHandle = udevp->os_param.reg_acc_handle; return ((void *)(udevp->os_param.regs_addr)); } /* mm_map_io_base */ /* * Name: mm_desc_size * * Input: ptr to LM's device structure, * descriptor type * * Return: size of the descriptor structure * * Description: This routine currently returns the size of packet descriptor * as defined by the UM module (lm_pkt_t is embedded in this * struct). This is used by LM's init routines trying to allocate * memory for TX/RX descriptor queues. */ u32_t mm_desc_size(lm_device_t *pdev, u32_t desc_type) { u32_t desc_size; switch (desc_type) { case DESC_TYPE_L2RX_PACKET: desc_size = sizeof (um_rxpacket_t); break; default: desc_size = 0; break; } desc_size = ALIGN_VALUE_TO_WORD_BOUNDARY(desc_size); return (desc_size); } /* mm_desc_size */ /* * Name: mm_get_user_config * * Input: ptr to LM's device structure * * Return: SUCCESS * * Description: This rotuine maps user option to corresponding parameters in * LM and UM device structures. */ lm_status_t mm_get_user_config(lm_device_t *pdev) { u32_t keep_vlan_tag = 0; u32_t offset; u32_t val; um_device_t *umdevice = (um_device_t *)pdev; bnx_cfg_init(umdevice); bnx_cfg_map_phy(umdevice); /* * If Management Firmware is running ensure that we don't * keep the VLAN tag, this is for older firmware */ offset = pdev->hw_info.shmem_base; offset += OFFSETOF(shmem_region_t, dev_info.port_feature_config.config); REG_RD_IND(pdev, offset, &val); if (!(val & PORT_FEATURE_MFW_ENABLED)) keep_vlan_tag = 1; /* * Newer versions of the firmware can handle VLAN tags * check to see if this version of the firmware can handle them */ offset = pdev->hw_info.shmem_base; offset += OFFSETOF(shmem_region_t, drv_fw_cap_mb.fw_cap_mb); REG_RD_IND(pdev, offset, &val); if ((val & FW_CAP_SIGNATURE) == FW_CAP_SIGNATURE) { if ((val & (FW_CAP_MFW_CAN_KEEP_VLAN | FW_CAP_BC_CAN_UPDATE_VLAN)) == (FW_CAP_MFW_CAN_KEEP_VLAN | FW_CAP_BC_CAN_UPDATE_VLAN)) { offset = pdev->hw_info.shmem_base; offset += OFFSETOF(shmem_region_t, drv_fw_cap_mb.drv_ack_cap_mb); REG_WR_IND(pdev, offset, DRV_ACK_CAP_SIGNATURE | FW_CAP_MFW_CAN_KEEP_VLAN | FW_CAP_BC_CAN_UPDATE_VLAN); keep_vlan_tag = 1; } } pdev->params.keep_vlan_tag = keep_vlan_tag; return (LM_STATUS_SUCCESS); } /* mm_get_user_config */ /* * Name: mm_alloc_mem * * Input: ptr to LM's device structure, * size of the memory block to be allocated * * Return: ptr to newly allocated memory region * * Description: This routine allocates memory region, updates the * resource list to reflect this newly allocated memory. */ void * mm_alloc_mem(lm_device_t *pdev, u32_t mem_size, void *resc_list) { void *memptr; bnx_memreq_t *memreq; um_device_t *umdevice; (void) resc_list; umdevice = (um_device_t *)pdev; if (mem_size == 0) { return (NULL); } if (umdevice->memcnt == BNX_MAX_MEMREQS) { cmn_err(CE_WARN, "%s: Lower module memreq overflow.\n", umdevice->dev_name); return (NULL); } memptr = kmem_zalloc(mem_size, KM_NOSLEEP); if (memptr == NULL) { cmn_err(CE_WARN, "%s: Failed to allocate local memory.\n", umdevice->dev_name); return (NULL); } memreq = &umdevice->memreq[umdevice->memcnt]; memreq->addr = memptr; memreq->size = mem_size; umdevice->memcnt++; return (memptr); } /* mm_alloc_mem */ /* * Name: mm_alloc_phys_mem * * Input: ptr to LM's device structure, * size of the memory block to be allocated, * pointer to store phys address, * memory type * * Return: virtual memory ptr to newly allocated memory region * * Description: This routine allocates memory region, updates the * resource list to reflect this newly allocated memory. * This function returns physical address in addition the * virtual address pointer. */ void * mm_alloc_phys_mem(lm_device_t *pdev, u32_t mem_size, lm_address_t *phys_mem, u8_t mem_type, void *resc_list) { int rc; caddr_t pbuf; um_device_t *udevp; size_t real_len; unsigned int count; ddi_dma_attr_t *dma_attrib; ddi_dma_handle_t *dma_handle; ddi_acc_handle_t *acc_handle; ddi_dma_cookie_t cookie; (void) mem_type; (void) resc_list; udevp = (um_device_t *)pdev; if (mem_size == 0) { return (NULL); } if (udevp->os_param.dma_handles_used == BNX_MAX_PHYS_MEMREQS) { cmn_err(CE_WARN, "%s: %s: Lower module phys memreq overflow.\n", udevp->dev_name, __func__); return (NULL); } if (!(mem_size & LM_PAGE_MASK)) { /* Size is multiple of page size. */ dma_attrib = &bnx_page_dma_attrib; } else { dma_attrib = &bnx_std_dma_attrib; } rc = udevp->os_param.dma_handles_used; dma_handle = &udevp->os_param.dma_handle[rc]; acc_handle = &udevp->os_param.dma_acc_handle[rc]; rc = ddi_dma_alloc_handle(udevp->os_param.dip, dma_attrib, DDI_DMA_DONTWAIT, (void *)0, dma_handle); if (rc != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: %s: Failed to alloc phys dma handle.\n", udevp->dev_name, __func__); return (NULL); } rc = ddi_dma_mem_alloc(*dma_handle, (size_t)mem_size + BNX_DMA_ALIGNMENT, &bnxAccessAttribBUF, DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, (void *)0, &pbuf, &real_len, acc_handle); if (rc != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: %s: Failed to alloc phys memory.\n", udevp->dev_name, __func__); goto error1; } rc = ddi_dma_addr_bind_handle(*dma_handle, (struct as *)0, pbuf, real_len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, (void *)0, &cookie, &count); if (rc != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: %s: Failed to bind DMA address.\n", udevp->dev_name, __func__); goto error2; } phys_mem->as_u64 = (u64_t)cookie.dmac_laddress; /* * Save the virtual memory address so * we can get the dma_handle later. */ udevp->os_param.dma_virt[udevp->os_param.dma_handles_used] = pbuf; udevp->os_param.dma_handles_used++; /* Zero the memory... */ bzero(pbuf, real_len); /* ...and make sure the new contents are flushed back to main memory. */ (void) ddi_dma_sync(*dma_handle, 0, real_len, DDI_DMA_SYNC_FORDEV); return (pbuf); error2: ddi_dma_mem_free(acc_handle); error1: ddi_dma_free_handle(dma_handle); return (NULL); } /* mm_alloc_phys_mem */ /* * Name: mm_indicate_tx * * Input: ptr to LM's device structure, * TX chain index, * array of pointers to packet descriptors, * number of packet descriptors in array * * Return: None * * Description: * Lower module calls this API function to return transmit packet * buffers to the system, and to allow the driver to reclaim * transmit resources. This function is only called upon transmit * abort and so is not in the fast path. */ void mm_indicate_tx(lm_device_t *pdev, u32_t chain_idx, struct _lm_packet_t *packet_arr[], u32_t num_packets) { um_txpacket_t **pkt_ptr; um_txpacket_t *pkt; s_list_t comp_list; pkt_ptr = (um_txpacket_t **)packet_arr; s_list_init(&comp_list, NULL, NULL, 0); while (num_packets) { pkt = *pkt_ptr; s_list_push_tail(&comp_list, &(pkt->lm_pkt.link)); pkt_ptr++; num_packets--; } bnx_xmit_ring_reclaim((um_device_t *)pdev, chain_idx, &comp_list); } /* mm_indicate_tx */ /* * Description: * * Return: */ static void bnx_display_link_msg(um_device_t * const umdevice) { char *media; char linkstr[128]; if (umdevice->dev_var.isfiber) { media = "Fiber"; } else { media = "Copper"; } if (umdevice->nddcfg.link_speed != 0) { (void) strlcpy(linkstr, "up (", sizeof (linkstr)); switch (umdevice->nddcfg.link_speed) { case 2500: (void) strlcat(linkstr, "2500Mbps, ", sizeof (linkstr)); break; case 1000: (void) strlcat(linkstr, "1000Mbps, ", sizeof (linkstr)); break; case 100: (void) strlcat(linkstr, "100Mbps, ", sizeof (linkstr)); break; case 10: (void) strlcat(linkstr, "10Mbps, ", sizeof (linkstr)); break; default: (void) strlcat(linkstr, "0Mbps, ", sizeof (linkstr)); } if (umdevice->nddcfg.link_duplex) { (void) strlcat(linkstr, "Full Duplex", sizeof (linkstr)); } else { (void) strlcat(linkstr, "Half Duplex", sizeof (linkstr)); } if (umdevice->nddcfg.link_tx_pause || umdevice->nddcfg.link_rx_pause) { (void) strlcat(linkstr, ", ", sizeof (linkstr)); if (umdevice->nddcfg.link_tx_pause) { (void) strlcat(linkstr, "Tx", sizeof (linkstr)); if (umdevice->nddcfg.link_rx_pause) { (void) strlcat(linkstr, " & Rx", sizeof (linkstr)); } } else { (void) strlcat(linkstr, "Rx", sizeof (linkstr)); } (void) strlcat(linkstr, " Flow Control ON", sizeof (linkstr)); } (void) strlcat(linkstr, ")", sizeof (linkstr)); } else { (void) snprintf(linkstr, sizeof (linkstr), "down"); } cmn_err(CE_NOTE, "!%s: %s link is %s", umdevice->dev_name, media, linkstr); } /* bnx_display_link_msg */ /* * Name: bnx_update_lp_cap * * Input: ptr to device structure * * Return: None * * Description: This function is updates link partners advertised * capabilities. */ static void bnx_update_lp_cap(um_device_t *const umdevice) { u32_t miireg; lm_status_t lmstatus; lm_device_t *lmdevice; lmdevice = &(umdevice->lm_dev); if (umdevice->dev_var.isfiber) { lmstatus = lm_mread(lmdevice, lmdevice->params.phy_addr, MII_REG(serdes_reg_t, mii_aneg_nxt_pg_rcv1), &miireg); if (lmstatus == LM_STATUS_SUCCESS) { if (miireg & MII_ANEG_NXT_PG_RCV1_2G5) { umdevice->remote.param_2500fdx = B_TRUE; } } lmstatus = lm_mread(lmdevice, lmdevice->params.phy_addr, PHY_LINK_PARTNER_ABILITY_REG, &miireg); if (lmstatus == LM_STATUS_SUCCESS) { miireg &= MII_ABILITY_PAUSE; if (miireg == MII_ADVERT_SYM_PAUSE) { umdevice->remote.param_tx_pause = B_TRUE; umdevice->remote.param_rx_pause = B_TRUE; } else if (miireg == MII_ADVERT_ASYM_PAUSE) { umdevice->remote.param_tx_pause = B_TRUE; } if (miireg & MII_ABILITY_FULL) { umdevice->remote.param_1000fdx = B_TRUE; } if (miireg & MII_ABILITY_HALF) { umdevice->remote.param_1000hdx = B_TRUE; } } } else { /* Copper */ lmstatus = lm_mread(lmdevice, lmdevice->params.phy_addr, PHY_1000BASET_STATUS_REG, &miireg); if (lmstatus == LM_STATUS_SUCCESS) { if (miireg & PHY_LINK_PARTNER_1000BASET_FULL) { umdevice->remote.param_1000fdx = B_TRUE; } if (miireg & PHY_LINK_PARTNER_1000BASET_HALF) { umdevice->remote.param_1000hdx = B_TRUE; } } lmstatus = lm_mread(lmdevice, lmdevice->params.phy_addr, PHY_LINK_PARTNER_ABILITY_REG, &miireg); if (lmstatus == LM_STATUS_SUCCESS) { if (miireg & PHY_LINK_PARTNER_PAUSE_CAPABLE) { umdevice->remote.param_tx_pause = B_TRUE; umdevice->remote.param_rx_pause = B_TRUE; } else if (miireg & PHY_LINK_PARTNER_ASYM_PAUSE) { umdevice->remote.param_tx_pause = B_TRUE; } if (miireg & PHY_LINK_PARTNER_100BASETX_FULL) { umdevice->remote.param_100fdx = B_TRUE; } if (miireg & PHY_LINK_PARTNER_100BASETX_HALF) { umdevice->remote.param_100hdx = B_TRUE; } if (miireg & PHY_LINK_PARTNER_10BASET_FULL) { umdevice->remote.param_10fdx = B_TRUE; } if (miireg & PHY_LINK_PARTNER_10BASET_HALF) { umdevice->remote.param_10hdx = B_TRUE; } } } #if 0 /* * If we can gather _any_ information about our link partner, then * because this information is exchanged through autonegotiation, we * know that our link partner supports autonegotiation. * * FIXME -- Find a more authoritative way to update link_autoneg. I'm * not sure it is legal, but it sounds possible to have autonegotiation * enabled on the remote end with no capabilities advertised. */ if (umdevice->remote.param_2500fdx || umdevice->remote.param_1000fdx || umdevice->remote.param_1000hdx || umdevice->remote.param_100fdx || umdevice->remote.param_100hdx || umdevice->remote.param_10fdx || umdevice->remote.param_10hdx || umdevice->remote.param_tx_pause || umdevice->remote.param_rx_pause) { umdevice->remote.param_autoneg = B_TRUE; } #else lmstatus = lm_mread(lmdevice, lmdevice->params.phy_addr, BCM540X_AUX_STATUS_REG, &miireg); if (lmstatus == LM_STATUS_SUCCESS) { if (miireg & BIT_12) { umdevice->remote.link_autoneg = B_TRUE; } } #endif } /* bnx_update_lp_cap */ /* * Name: mm_indicate_link * * Input: ptr to LM's device structure, * link status, * lm_medium_t struct * * Return: None * * Description: Lower module calls this function when ever there is a network * link status change. This routine updates the driver data * structure as well calls gld_linkstate() to notify event to GLD. */ void mm_indicate_link(lm_device_t *lmdevice, lm_status_t link, lm_medium_t medium) { int link_speed; um_device_t *umdevice; umdevice = (um_device_t *)lmdevice; if (umdevice->link_updates_ok == B_FALSE) { return; } /* ignore link status if it has not changed since the last indicate */ if ((umdevice->dev_var.indLink == link) && (umdevice->dev_var.indMedium == medium)) { return; } umdevice->dev_var.indLink = link; umdevice->dev_var.indMedium = medium; switch (GET_MEDIUM_SPEED(medium)) { case LM_MEDIUM_SPEED_10MBPS: link_speed = 10; break; case LM_MEDIUM_SPEED_100MBPS: link_speed = 100; break; case LM_MEDIUM_SPEED_1000MBPS: link_speed = 1000; break; case LM_MEDIUM_SPEED_2500MBPS: link_speed = 2500; break; default: link_speed = 0; break; } /* * Validate the linespeed against known hardware capabilities. * This is a common occurance. */ if (umdevice->dev_var.isfiber) { if (link_speed != 2500 && link_speed != 1000) { link_speed = 0; } } if (link_speed == 0) { link = LM_STATUS_LINK_DOWN; } /* * If neither link-up or link-down flag is present, then there must * have been multiple link events. Do the right thing. */ if (link != LM_STATUS_LINK_ACTIVE && link != LM_STATUS_LINK_DOWN) { /* Fill in the missing information. */ if (link_speed != 0) { link = LM_STATUS_LINK_ACTIVE; } else { link = LM_STATUS_LINK_DOWN; } } #if 0 if (((umdevice->nddcfg.link_speed == 0) && (link != LM_STATUS_LINK_ACTIVE)) || ((umdevice->nddcfg.link_speed != 0) && (link != LM_STATUS_LINK_DOWN))) { /* This is a false notification. */ return; } #endif if (umdevice->timer_link_check_interval) { if (link == LM_STATUS_LINK_ACTIVE) { if (lmdevice->vars.serdes_fallback_status) { /* * Start the timer to poll the serdes for * reception of configs from the link partner. * When this happens the remote has autoneg * enabled and we'll restart our autoneg. */ bnx_link_timer_restart(umdevice); } } else { if (umdevice->timer_link_check_counter) { bnx_link_timer_restart(umdevice); } } } if (link == LM_STATUS_LINK_DOWN) { umdevice->nddcfg.link_speed = 0; umdevice->nddcfg.link_duplex = B_FALSE; umdevice->nddcfg.link_tx_pause = B_FALSE; umdevice->nddcfg.link_rx_pause = B_FALSE; umdevice->remote.link_autoneg = B_FALSE; umdevice->remote.param_2500fdx = B_FALSE; umdevice->remote.param_1000fdx = B_FALSE; umdevice->remote.param_1000hdx = B_FALSE; umdevice->remote.param_100fdx = B_FALSE; umdevice->remote.param_100hdx = B_FALSE; umdevice->remote.param_10fdx = B_FALSE; umdevice->remote.param_10hdx = B_FALSE; umdevice->remote.param_tx_pause = B_FALSE; umdevice->remote.param_rx_pause = B_FALSE; bnx_display_link_msg(umdevice); bnx_gld_link(umdevice, LINK_STATE_DOWN); } else if (link == LM_STATUS_LINK_ACTIVE) { umdevice->nddcfg.link_speed = link_speed; if (GET_MEDIUM_DUPLEX(medium)) { /* half duplex */ umdevice->nddcfg.link_duplex = B_FALSE; } else { /* full duplex */ umdevice->nddcfg.link_duplex = B_TRUE; } if (lmdevice->vars.flow_control & LM_FLOW_CONTROL_TRANSMIT_PAUSE) { umdevice->nddcfg.link_tx_pause = B_TRUE; } else { umdevice->nddcfg.link_tx_pause = B_FALSE; } if (lmdevice->vars.flow_control & LM_FLOW_CONTROL_RECEIVE_PAUSE) { umdevice->nddcfg.link_rx_pause = B_TRUE; } else { umdevice->nddcfg.link_rx_pause = B_FALSE; } if (umdevice->curcfg.lnkcfg.link_autoneg == B_TRUE) { bnx_update_lp_cap(umdevice); } bnx_display_link_msg(umdevice); bnx_gld_link(umdevice, LINK_STATE_UP); } } /* mm_indicate_link */ /* * Description: * * Return: */ void mm_acquire_ind_reg_lock(struct _lm_device_t *pdev) { um_device_t *umdevice; umdevice = (um_device_t *)pdev; mutex_enter(&umdevice->os_param.ind_mutex); } /* mm_acquire_ind_reg_lock */ /* * Description: * * Return: */ void mm_release_ind_reg_lock(struct _lm_device_t *pdev) { um_device_t *umdevice; umdevice = (um_device_t *)pdev; mutex_exit(&umdevice->os_param.ind_mutex); } /* mm_release_ind_reg_lock */