/* * 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 "bnxgld.h" #include "bnxhwi.h" #include "bnxint.h" #include "bnxtmr.h" #include "bnxcfg.h" #define BNX_PRODUCT_BANNER "QLogic 570x/571x Gigabit Ethernet Driver "\ BRCMVERSION #define BNX_PRODUCT_INFO "QLogic 570x/571x GbE "\ BRCMVERSION ddi_device_acc_attr_t bnxAccessAttribBAR = { DDI_DEVICE_ATTR_V0, /* devacc_attr_version */ DDI_STRUCTURE_LE_ACC, /* devacc_attr_endian_flags */ DDI_STRICTORDER_ACC, /* devacc_attr_dataorder */ DDI_DEFAULT_ACC /* devacc_attr_access */ }; ddi_device_acc_attr_t bnxAccessAttribBUF = { DDI_DEVICE_ATTR_V0, /* devacc_attr_version */ DDI_NEVERSWAP_ACC, /* devacc_attr_endian_flags */ DDI_STRICTORDER_ACC, /* devacc_attr_dataorder */ DDI_DEFAULT_ACC /* devacc_attr_access */ }; /* * Name: bnx_free_system_resources * * Input: ptr to device structure * * Return: void * * Description: * This function is called from detach() entry point to free most * resources held by this device instance. */ static int bnx_free_system_resources(um_device_t * const umdevice) { if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_MINOR_NODE) { umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_MINOR_NODE; #ifdef _USE_FRIENDLY_NAME ddi_remove_minor_node(umdevice->os_param.dip, (char *)ddi_driver_name(umdevice->os_param.dip)); #else ddi_remove_minor_node(umdevice->os_param.dip, ddi_get_name(umdevice->os_param.dip)); #endif } if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_TIMER) { umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_TIMER; bnx_timer_fini(umdevice); } if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_GLD_REGISTER) { if (bnx_gld_fini(umdevice)) { /* * FIXME -- If bnx_gld_fini() fails, we need to * reactivate resources. */ return (-1); } umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_GLD_REGISTER; } if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_KSTAT) { umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_KSTAT; bnx_kstat_fini(umdevice); } if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_HDWR_REGISTER) { umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_HDWR_REGISTER; bnx_hdwr_fini(umdevice); } if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_MUTEX) { umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_MUTEX; mutex_destroy(&umdevice->os_param.ind_mutex); mutex_destroy(&umdevice->os_param.phy_mutex); mutex_destroy(&umdevice->os_param.rcv_mutex); } if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_INTR_1) { umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_INTR_1; bnxIntrFini(umdevice); } if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_MAP_REGS) { umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_MAP_REGS; ddi_regs_map_free(&umdevice->os_param.reg_acc_handle); umdevice->lm_dev.vars.dmaRegAccHandle = NULL; umdevice->os_param.reg_acc_handle = NULL; } if (umdevice->os_param.active_resc_flag & DRV_RESOURCE_PCICFG_MAPPED) { umdevice->os_param.active_resc_flag &= ~DRV_RESOURCE_PCICFG_MAPPED; pci_config_teardown(&umdevice->os_param.pci_cfg_handle); } return (0); } /* * Name: bnx_attach_attach * * Input: ptr to dev_info_t * * Return: DDI_SUCCESS or DDI_FAILURE. * * Description: This is the main code involving all important driver data struct * and device initialization stuff. This function allocates driver * soft state for this instance of the driver, sets access up * attributes for the device, maps BAR register space, initializes * the hardware, determines interrupt pin, registers interrupt * service routine with the OS and initializes receive/transmit * mutex. After successful completion of above mentioned tasks, * the driver registers with the GLD and creates minor node in * the file system tree for this device. */ static int bnx_attach_attach(um_device_t *umdevice) { int rc; int instance; unsigned int val; int chip_id; int device_id; int subdevice_id; off_t regSize; dev_info_t *dip; dip = umdevice->os_param.dip; umdevice->os_param.active_resc_flag = 0; rc = pci_config_setup(umdevice->os_param.dip, &umdevice->os_param.pci_cfg_handle); if (rc != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: Failed to setup PCI configuration space accesses.\n", umdevice->dev_name); goto error; } umdevice->os_param.active_resc_flag |= DRV_RESOURCE_PCICFG_MAPPED; rc = ddi_dev_regsize(dip, 1, ®Size); if (rc != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: failed to determine register set size.", umdevice->dev_name); } /* * Setup device memory mapping so that LM driver can start accessing it. */ rc = ddi_regs_map_setup(dip, 1, /* BAR */ &umdevice->os_param.regs_addr, 0, /* OFFSET */ regSize, &bnxAccessAttribBAR, &umdevice->os_param.reg_acc_handle); if (rc != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: Failed to memory map device.\n", umdevice->dev_name); goto error; } umdevice->os_param.active_resc_flag |= DRV_RESOURCE_MAP_REGS; bnx_cfg_msix(umdevice); if (bnxIntrInit(umdevice) != 0) { goto error; } umdevice->os_param.active_resc_flag |= DRV_RESOURCE_INTR_1; mutex_init(&umdevice->os_param.rcv_mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(umdevice->intrPriority)); mutex_init(&umdevice->os_param.phy_mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(umdevice->intrPriority)); mutex_init(&umdevice->os_param.ind_mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(umdevice->intrPriority)); umdevice->os_param.active_resc_flag |= DRV_RESOURCE_MUTEX; /* * Call lower module's initialization routines to initialize * hardware and related components within BNX. */ if (bnx_hdwr_init(umdevice)) { goto error; } umdevice->os_param.active_resc_flag |= DRV_RESOURCE_HDWR_REGISTER; if (!bnx_kstat_init(umdevice)) { goto error; } umdevice->os_param.active_resc_flag |= DRV_RESOURCE_KSTAT; if (bnx_gld_init(umdevice)) { goto error; } umdevice->os_param.active_resc_flag |= DRV_RESOURCE_GLD_REGISTER; bnx_timer_init(umdevice); umdevice->os_param.active_resc_flag |= DRV_RESOURCE_TIMER; instance = ddi_get_instance(umdevice->os_param.dip); /* Create a minor node entry in /devices . */ #ifdef _USE_FRIENDLY_NAME rc = ddi_create_minor_node(dip, (char *)ddi_driver_name(dip), S_IFCHR, instance, DDI_PSEUDO, 0); #else rc = ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR, instance, DDI_PSEUDO, 0); #endif if (rc == DDI_FAILURE) { cmn_err(CE_WARN, "%s: Failed to create device minor node.\n", umdevice->dev_name); goto error; } umdevice->os_param.active_resc_flag |= DRV_RESOURCE_MINOR_NODE; ddi_report_dev(dip); device_id = pci_config_get16(umdevice->os_param.pci_cfg_handle, 0x2); subdevice_id = pci_config_get16(umdevice->os_param.pci_cfg_handle, 0x2e); /* Dip into PCI config space to determine if we have 5716's */ if ((device_id == 0x163b) && (subdevice_id == 0x163b)) { chip_id = 0x5716; } else { chip_id = CHIP_NUM(&umdevice->lm_dev) >> 16; } (void) snprintf(umdevice->version, sizeof (umdevice->version), "%s", BRCMVERSION); /* Get firmware version. */ REG_RD_IND(&umdevice->lm_dev, umdevice->lm_dev.hw_info.shmem_base + OFFSETOF(shmem_region_t, dev_info.bc_rev), &val); umdevice->dev_var.fw_ver = (val & 0xFFFF0000) | ((val & 0xFF00) >> 8); (void) snprintf(umdevice->versionFW, sizeof (umdevice->versionFW), "0x%x", umdevice->dev_var.fw_ver); (void) snprintf(umdevice->chipName, sizeof (umdevice->chipName), "BCM%x", chip_id); (void) snprintf(umdevice->intrAlloc, sizeof (umdevice->intrAlloc), "1 %s", (umdevice->intrType == DDI_INTR_TYPE_MSIX) ? "MSIX" : (umdevice->intrType == DDI_INTR_TYPE_MSI) ? "MSI" : "Fixed"); cmn_err(CE_NOTE, "!%s: (%s) BCM%x device with F/W Ver%x is initialized (%s)", umdevice->dev_name, umdevice->version, chip_id, umdevice->dev_var.fw_ver, umdevice->intrAlloc); return (0); error: (void) bnx_free_system_resources(umdevice); return (-1); } /* * Name: bnx_attach * * Input: ptr to dev_info_t, command code for the task to be executed * * Return: DDI_SUCCESS or DDI_FAILURE. * * Description: OS determined module attach entry point. */ static int bnx_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { um_device_t *umdevice; int ret_val = DDI_SUCCESS; switch (cmd) { case DDI_ATTACH: umdevice = kmem_zalloc(sizeof (um_device_t), KM_NOSLEEP); if (umdevice == NULL) { cmn_err(CE_WARN, "%s: Failed to allocate " "device memory.\n", __func__); ret_val = DDI_FAILURE; break; } /* Save dev_info_t info in the driver struture. */ umdevice->os_param.dip = dip; /* * Obtain a human-readable name to prepend all our * messages with. */ umdevice->instance = ddi_get_instance(dip); (void) snprintf(umdevice->dev_name, sizeof (umdevice->dev_name), "%s%d", "bnx", umdevice->instance); /* * Set driver private pointer to per device structure * ptr. */ ddi_set_driver_private(dip, (caddr_t)umdevice); umdevice->magic = BNX_MAGIC; if (bnx_attach_attach(umdevice)) { ddi_set_driver_private(dip, (caddr_t)NULL); kmem_free(umdevice, sizeof (um_device_t)); ret_val = DDI_FAILURE; } break; case DDI_RESUME: /* Retrieve our device structure. */ umdevice = ddi_get_driver_private(dip); if (umdevice == NULL) { ret_val = DDI_FAILURE; break; } break; default: ret_val = DDI_FAILURE; break; } return (ret_val); } /* * Name: bnx_detach * * Input: ptr to dev_info_t, command code for the task to be executed * * Return: DDI_SUCCESS or DDI_FAILURE. * * Description: OS determined module detach entry point. */ static int bnx_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { um_device_t *umdevice; int ret_val = DDI_SUCCESS; switch (cmd) { case DDI_DETACH: umdevice = ddi_get_driver_private(dip); if (umdevice == NULL) { /* Must have failed attach. */ ret_val = DDI_SUCCESS; break; } /* Sanity check. */ if (umdevice == NULL) { cmn_err(CE_WARN, "%s: Sanity check failed(1).", __func__); ret_val = DDI_SUCCESS; break; } /* Sanity check. */ if (umdevice->os_param.dip != dip) { cmn_err(CE_WARN, "%s: Sanity check failed(2).", __func__); ret_val = DDI_SUCCESS; break; } /* Another sanity check. */ if (umdevice->intr_enabled != B_FALSE) { cmn_err(CE_WARN, "%s: Detaching a device " "that is currently running!!!\n", umdevice->dev_name); ret_val = DDI_FAILURE; break; } if (bnx_free_system_resources(umdevice)) { ret_val = DDI_FAILURE; break; } ddi_set_driver_private(dip, (caddr_t)NULL); kmem_free(umdevice, sizeof (um_device_t)); break; case DDI_SUSPEND: /* Retrieve our device structure. */ umdevice = ddi_get_driver_private(dip); if (umdevice == NULL) { ret_val = DDI_FAILURE; break; } break; default: ret_val = DDI_FAILURE; break; } return (ret_val); } /* * Name: bnx_quiesce * * Input: ptr to dev_info_t * * Return: DDI_SUCCESS or DDI_FAILURE. * * Description: quiesce(9E) entry point. * This function will make sure no more interrupts and DMA of * the hardware. It is called when the system is single-threaded * at high PIL with preemption disabled. Thus this function should * not be blocked. */ static int bnx_quiesce(dev_info_t *dip) { um_device_t *umdevice; umdevice = ddi_get_driver_private(dip); /* Sanity check. */ if (umdevice == NULL || umdevice->os_param.dip != dip) { cmn_err(CE_WARN, "%s: Sanity check failed.", __func__); return (DDI_FAILURE); } /* Stop the device from generating any interrupts. */ lm_disable_int(&(umdevice->lm_dev)); /* Set RX mask to stop receiving any further packets */ (void) lm_set_rx_mask(&(umdevice->lm_dev), RX_FILTER_USER_IDX0, LM_RX_MASK_ACCEPT_NONE); return (DDI_SUCCESS); } DDI_DEFINE_STREAM_OPS(bnx_dev_ops, nulldev, nulldev, bnx_attach, bnx_detach, \ nodev, NULL, (D_MP | D_64BIT), NULL, bnx_quiesce); static struct modldrv bnx_modldrv = { &mod_driverops, /* drv_modops */ BNX_PRODUCT_INFO, /* drv_linkinfo */ &bnx_dev_ops /* drv_dev_ops */ }; static struct modlinkage bnx_modlinkage = { MODREV_1, /* ml_rev */ &bnx_modldrv, /* ml_linkage */ NULL /* NULL termination */ }; /* * Name: _init * * Input: None * * Return: SUCCESS or FAILURE. * * Description: OS determined driver module load entry point. */ int _init(void) { int rc; mac_init_ops(&bnx_dev_ops, "bnx"); /* Install module information with O/S */ rc = mod_install(&bnx_modlinkage); if (rc != 0) { cmn_err(CE_WARN, "%s:_init - mod_install returned 0x%x", "bnx", rc); return (rc); } cmn_err(CE_NOTE, "!%s", BNX_PRODUCT_BANNER); return (rc); } /* * Name: _fini * * Input: None * * Return: SUCCESS or FAILURE. * * Description: OS determined driver module unload entry point. */ int _fini(void) { int rc; rc = mod_remove(&bnx_modlinkage); if (rc == 0) { mac_fini_ops(&bnx_dev_ops); } return (rc); } /* * Name: _info * * Input: None * * Return: SUCCESS or FAILURE. * * Description: OS determined module info entry point. */ int _info(struct modinfo *modinfop) { int rc; rc = mod_info(&bnx_modlinkage, modinfop); return (rc); }