/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * rtls -- REALTEK 8139-serials PCI Fast Ethernet Driver, Depends on the * Generic LAN Driver utility functions in /kernel/misc/mac * * This product is covered by one or more of the following patents: * US5,307,459, US5,434,872, US5,732,094, US6,570,884, US6,115,776, and * US6,327,625. * * Currently supports: * RTL8139 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rtls.h" /* * Declarations and Module Linkage */ /* * This is the string displayed by modinfo, etc. */ static char rtls_ident[] = "RealTek 8139 Ethernet driver"; #ifdef RTLS_DEBUG int rtls_debug = 0; #endif /* * Required system entry points */ static int rtls_attach(dev_info_t *, ddi_attach_cmd_t); static int rtls_detach(dev_info_t *, ddi_detach_cmd_t); static int rtls_quiesce(dev_info_t *); /* * Required driver entry points for MAC */ static int rtls_m_start(void *); static void rtls_m_stop(void *); static int rtls_m_unicst(void *, const uint8_t *); static int rtls_m_multicst(void *, boolean_t, const uint8_t *); static int rtls_m_promisc(void *, boolean_t); static mblk_t *rtls_m_tx(void *, mblk_t *); static int rtls_m_stat(void *, uint_t, uint64_t *); static uint_t rtls_intr(caddr_t); /* * Internal functions used by the above entry points */ static int rtls_chip_reset(rtls_t *, boolean_t); static void rtls_chip_init(rtls_t *); static void rtls_chip_stop(rtls_t *rtlsp); static void rtls_chip_start(rtls_t *rtlsp); static void rtls_chip_restart(rtls_t *rtlsp); static void rtls_get_mac_addr(rtls_t *, uint8_t *); static void rtls_set_mac_addr(rtls_t *, const uint8_t *); static uint_t rtls_hash_index(const uint8_t *); static boolean_t rtls_send(rtls_t *, mblk_t *); static void rtls_receive(rtls_t *); static void rtls_periodic(void *); static uint_t rtls_reschedule(caddr_t arg); static uint_t rtls_events(caddr_t arg); static void rtls_chip_force_speed_duplex(rtls_t *); /* * Buffer Management Routines */ static int rtls_alloc_bufs(rtls_t *); static void rtls_free_bufs(rtls_t *); static int rtls_alloc_dma_mem(rtls_t *, size_t, ddi_device_acc_attr_t *, uint_t, dma_area_t *); static void rtls_free_dma_mem(dma_area_t *); #ifdef RTLS_DEBUG static void rtls_reg_print(rtls_t *); /* debug routine */ #endif #define RTLS_DRIVER_NAME "rtls" /* * Used for buffers allocated by ddi_dma_mem_alloc() */ static ddi_dma_attr_t dma_attr = { DMA_ATTR_V0, /* dma_attr version */ 0, /* dma_attr_addr_lo */ (uint_t)0xFFFFFFFF, /* dma_attr_addr_hi */ 0x7FFFFFFF, /* dma_attr_count_max */ 4, /* dma_attr_align */ 0x3F, /* dma_attr_burstsizes */ 1, /* dma_attr_minxfer */ (uint_t)0xFFFFFFFF, /* dma_attr_maxxfer */ (uint_t)0xFFFFFFFF, /* dma_attr_seg */ 1, /* dma_attr_sgllen */ 1, /* dma_attr_granular */ 0, /* dma_attr_flags */ }; /* * PIO access attributes for registers */ static ddi_device_acc_attr_t rtls_reg_accattr = { DDI_DEVICE_ATTR_V0, DDI_STRUCTURE_LE_ACC, DDI_STRICTORDER_ACC }; /* * DMA access attributes for data */ static ddi_device_acc_attr_t rtls_buf_accattr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, DDI_STRICTORDER_ACC }; uchar_t rtls_broadcastaddr[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; static mac_callbacks_t rtls_m_callbacks = { 0, rtls_m_stat, rtls_m_start, rtls_m_stop, rtls_m_promisc, rtls_m_multicst, rtls_m_unicst, rtls_m_tx, NULL, /* mc_resources */ NULL, /* mc_ioctl */ NULL /* mc_getcapab */ }; DDI_DEFINE_STREAM_OPS(rtls_dev_ops, nulldev, nulldev, rtls_attach, rtls_detach, nodev, NULL, D_MP, NULL, rtls_quiesce); /* * Standard module linkage initialization for a MAC driver */ static struct modldrv rtls_modldrv = { &mod_driverops, /* type of module. This one is a driver */ rtls_ident, /* short description */ &rtls_dev_ops /* driver specific ops */ }; static struct modlinkage modlinkage = { MODREV_1, { (void *)&rtls_modldrv, NULL } }; /* Save ForceSpeedDuplex value for instances in rtls.conf file */ #define RTLS_MAX_DEVICES 256 static uint_t rtls_link_mode[RTLS_MAX_DEVICES]; /* * ========== RealTek chip register access Routines ========== */ static uint8_t rtls_reg_get8(rtls_t *rtlsp, uint32_t reg); #pragma inline(rtls_reg_get8) static uint8_t rtls_reg_get8(rtls_t *rtlsp, uint32_t reg) { uint8_t *addr; addr = REG8(rtlsp->io_reg, reg); return (ddi_get8(rtlsp->io_handle, addr)); } static uint16_t rtls_reg_get16(rtls_t *rtlsp, uint32_t reg); #pragma inline(rtls_reg_get16) static uint16_t rtls_reg_get16(rtls_t *rtlsp, uint32_t reg) { uint16_t *addr; addr = REG16(rtlsp->io_reg, reg); return (ddi_get16(rtlsp->io_handle, addr)); } static uint32_t rtls_reg_get32(rtls_t *rtlsp, uint32_t reg); #pragma inline(rtls_reg_get32) static uint32_t rtls_reg_get32(rtls_t *rtlsp, uint32_t reg) { uint32_t *addr; addr = REG32(rtlsp->io_reg, reg); return (ddi_get32(rtlsp->io_handle, addr)); } static void rtls_reg_set8(rtls_t *rtlsp, uint32_t reg, uint8_t value); #pragma inline(rtls_reg_set8) static void rtls_reg_set8(rtls_t *rtlsp, uint32_t reg, uint8_t value) { uint8_t *addr; addr = REG8(rtlsp->io_reg, reg); ddi_put8(rtlsp->io_handle, addr, value); } static void rtls_reg_set16(rtls_t *rtlsp, uint32_t reg, uint16_t value); #pragma inline(rtls_reg_set16) static void rtls_reg_set16(rtls_t *rtlsp, uint32_t reg, uint16_t value) { uint16_t *addr; addr = REG16(rtlsp->io_reg, reg); ddi_put16(rtlsp->io_handle, addr, value); } static void rtls_reg_set32(rtls_t *rtlsp, uint32_t reg, uint32_t value); #pragma inline(rtls_reg_set32) static void rtls_reg_set32(rtls_t *rtlsp, uint32_t reg, uint32_t value) { uint32_t *addr; addr = REG32(rtlsp->io_reg, reg); ddi_put32(rtlsp->io_handle, addr, value); } /* * ========== Module Loading Entry Points ========== */ int _init(void) { int rv; mac_init_ops(&rtls_dev_ops, RTLS_DRIVER_NAME); if ((rv = mod_install(&modlinkage)) != DDI_SUCCESS) { mac_fini_ops(&rtls_dev_ops); } return (rv); } int _fini(void) { int rv; if ((rv = mod_remove(&modlinkage)) == DDI_SUCCESS) { mac_fini_ops(&rtls_dev_ops); } return (rv); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* * ========== DDI Entry Points ========== */ /* * attach(9E) -- Attach a device to the system * * Called once for each board successfully probed. */ static int rtls_attach(dev_info_t *devinfo, ddi_attach_cmd_t cmd) { rtls_t *rtlsp; /* Our private device info */ ddi_acc_handle_t pci_handle; uint16_t pci_commond; uint16_t vendorid; uint16_t deviceid; uint32_t device; mac_register_t *macp; int err; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: if ((rtlsp = ddi_get_driver_private(devinfo)) == NULL) { return (DDI_FAILURE); } mutex_enter(&rtlsp->rtls_io_lock); mutex_enter(&rtlsp->rtls_rx_lock); mutex_enter(&rtlsp->rtls_tx_lock); /* * Turn on Master Enable (DMA) and IO Enable bits. * Enable PCI Memory Space accesses * Disable Memory Write/Invalidate */ if (pci_config_setup(devinfo, &pci_handle) != DDI_SUCCESS) { mutex_exit(&rtlsp->rtls_tx_lock); mutex_exit(&rtlsp->rtls_rx_lock); mutex_exit(&rtlsp->rtls_io_lock); return (DDI_FAILURE); } pci_commond = pci_config_get16(pci_handle, PCI_CONF_COMM); pci_commond &= ~PCI_COMM_MEMWR_INVAL; pci_commond |= PCI_COMM_ME | PCI_COMM_MAE | PCI_COMM_IO; pci_config_put32(pci_handle, PCI_CONF_COMM, pci_commond); pci_config_teardown(&pci_handle); rtls_chip_restart(rtlsp); rtlsp->chip_error = B_FALSE; rtlsp->tx_retry = 0; rtlsp->rtls_suspended = B_FALSE; mutex_exit(&rtlsp->rtls_tx_lock); mutex_exit(&rtlsp->rtls_rx_lock); mutex_exit(&rtlsp->rtls_io_lock); mac_tx_update(rtlsp->mh); return (DDI_SUCCESS); default: return (DDI_FAILURE); } /* * we don't support high level interrupts in the driver */ if (ddi_intr_hilevel(devinfo, 0) != 0) { cmn_err(CE_WARN, "rtls_attach -- unsupported high level interrupt"); return (DDI_FAILURE); } /* * Get handle to access pci configuration space */ if (pci_config_setup(devinfo, &pci_handle) != DDI_SUCCESS) { cmn_err(CE_WARN, "rtls_attach -- pci_config_setup fail."); return (DDI_FAILURE); } /* * Make sure we support this particular vendor/device */ vendorid = pci_config_get16(pci_handle, PCI_CONF_VENID); deviceid = pci_config_get16(pci_handle, PCI_CONF_DEVID); device = vendorid; device = (device << 16) | deviceid; /* combine two id together */ #ifdef RTLS_DEBUG cmn_err(CE_NOTE, "rtls_attach: vendorID = 0x%x, deviceID = 0x%x", vendorid, deviceid); #endif /* * See if we support this device * We do not return for wrong device id. It's user risk. */ switch (device) { default: cmn_err(CE_WARN, "RTLS don't support this device: " "vendorID = 0x%x, deviceID = 0x%x", vendorid, deviceid); break; case RTLS_SUPPORT_DEVICE_1: case RTLS_SUPPORT_DEVICE_2: case RTLS_SUPPORT_DEVICE_3: break; } #ifdef RTLS_OEM /* * RealTek required macro */ if ((vendorid != RT_VENDOR_ID) || (deviceid != RT_DEVICE_8139)) { cmn_err(CE_WARN, "rtls_attach -- wrong OEM device: " "vendorID = 0x%x, deviceID = 0x%x", vendorid, deviceid); pci_config_teardown(&pci_handle); return (DDI_FAILURE); } #endif /* * Turn on Master Enable (DMA) and IO Enable bits. * Enable PCI Memory Space accesses * Disable Memory Write/Invalidate */ pci_commond = pci_config_get16(pci_handle, PCI_CONF_COMM); pci_commond &= ~PCI_COMM_MEMWR_INVAL; pci_commond |= PCI_COMM_ME | PCI_COMM_MAE | PCI_COMM_IO; pci_config_put32(pci_handle, PCI_CONF_COMM, pci_commond); /* * Free handle to access pci configuration space */ pci_config_teardown(&pci_handle); rtlsp = kmem_zalloc(sizeof (rtls_t), KM_SLEEP); if (rtlsp == NULL) { cmn_err(CE_WARN, "rtls_attach -- rtls_t alloc fail."); return (DDI_FAILURE); } ddi_set_driver_private(devinfo, rtlsp); rtlsp->devinfo = devinfo; rtlsp->instance = ddi_get_instance(devinfo); /* * Map operating register */ err = ddi_regs_map_setup(devinfo, 1, &rtlsp->io_reg, (offset_t)0, 0, &rtls_reg_accattr, &rtlsp->io_handle); if (err != DDI_SUCCESS) { kmem_free((caddr_t)rtlsp, sizeof (rtls_t)); cmn_err(CE_WARN, "rtls_attach -- ddi_regs_map_setup fail."); return (DDI_FAILURE); } /* * Allocate the TX and RX descriptors/buffers */ if (rtls_alloc_bufs(rtlsp) == DDI_FAILURE) { cmn_err(CE_WARN, "rtls_attach -- DMA buffer allocation fail."); goto fail; } /* * Reset the chip */ err = rtls_chip_reset(rtlsp, B_FALSE); if (err != DDI_SUCCESS) goto fail; /* * Init rtls_t structure */ rtls_get_mac_addr(rtlsp, rtlsp->netaddr); /* * Add the softint handlers: * * Both of these handlers are used to avoid restrictions on the * context and/or mutexes required for some operations. In * particular, the hardware interrupt handler and its subfunctions * can detect a number of conditions that we don't want to handle * in that context or with that set of mutexes held. So, these * softints are triggered instead: * * the softint is triggered if we have previously * had to refuse to send a packet because of resource shortage * (we've run out of transmit descriptors),Its only purpose is to * call mac_tx_update() to retry the pending transmits (we're not * allowed to hold driver-defined mutexes across mac_tx_update()). * * the is triggered if the h/w interrupt handler * sees the . */ if (ddi_add_softintr(devinfo, DDI_SOFTINT_LOW, &rtlsp->resched_id, NULL, NULL, rtls_reschedule, (caddr_t)rtlsp) != DDI_SUCCESS) { cmn_err(CE_WARN, "rtls_attach -- ddi_add_softintr 1 fail."); goto fail; } if (ddi_add_softintr(devinfo, DDI_SOFTINT_LOW, &rtlsp->events_id, NULL, NULL, rtls_events, (caddr_t)rtlsp) != DDI_SUCCESS) { cmn_err(CE_WARN, "rtls_attach -- ddi_add_softintr 2 fail."); ddi_remove_softintr(rtlsp->resched_id); goto fail; } /* * Add the interrupt handler * * This will prevent receiving interrupts before device is ready, as * we are initializing device after setting the interrupts. So we * will not get our interrupt handler invoked by OS while our device * is still coming up or timer routines will not start till we are * all set to process... */ if (ddi_add_intr(devinfo, 0, &rtlsp->iblk, NULL, rtls_intr, (caddr_t)rtlsp) != DDI_SUCCESS) { cmn_err(CE_WARN, "rtls_attach -- ddi_add_intr fail."); goto late_fail; } if ((macp = mac_alloc(MAC_VERSION)) == NULL) { cmn_err(CE_WARN, "rtls_attach - mac_alloc fail."); ddi_remove_intr(devinfo, 0, rtlsp->iblk); goto late_fail; } /* * Init mutex */ mutex_init(&rtlsp->rtls_io_lock, NULL, MUTEX_DRIVER, rtlsp->iblk); mutex_init(&rtlsp->rtls_tx_lock, NULL, MUTEX_DRIVER, rtlsp->iblk); mutex_init(&rtlsp->rtls_rx_lock, NULL, MUTEX_DRIVER, rtlsp->iblk); /* * Initialize pointers to device specific functions which will be * used by the generic layer. */ macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER; macp->m_driver = rtlsp; macp->m_dip = devinfo; macp->m_src_addr = rtlsp->netaddr; macp->m_callbacks = &rtls_m_callbacks; macp->m_min_sdu = 0; macp->m_max_sdu = ETHERMTU; macp->m_margin = VLAN_TAGSZ; if (mac_register(macp, &rtlsp->mh) != 0) { ddi_remove_intr(devinfo, 0, rtlsp->iblk); mutex_destroy(&rtlsp->rtls_io_lock); mutex_destroy(&rtlsp->rtls_tx_lock); mutex_destroy(&rtlsp->rtls_rx_lock); goto late_fail; } mac_free(macp); /* * Add the periodic timer to check link state. We do this * once a second, in case the link interrupt is missed (or not * delivered -- qemu emulated 8139 devices don't deliver the * link change interrupt.) This can run in ordinary kernel * context. */ rtlsp->periodic_id = ddi_periodic_add(rtls_periodic, rtlsp, 1000000000U, 0); ASSERT(rtlsp->periodic_id != NULL); /* API guarantee */ return (DDI_SUCCESS); late_fail: if (macp) mac_free(macp); ddi_remove_softintr(rtlsp->resched_id); ddi_remove_softintr(rtlsp->events_id); fail: ddi_regs_map_free(&rtlsp->io_handle); rtls_free_bufs(rtlsp); kmem_free(rtlsp, sizeof (rtls_t)); return (DDI_FAILURE); } /* * detach(9E) -- Detach a device from the system */ static int rtls_detach(dev_info_t *devinfo, ddi_detach_cmd_t cmd) { rtls_t *rtlsp; /* our private device info */ /* * Get the driver private structure */ if ((rtlsp = ddi_get_driver_private(devinfo)) == NULL) { return (DDI_FAILURE); } switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: mutex_enter(&rtlsp->rtls_io_lock); mutex_enter(&rtlsp->rtls_rx_lock); mutex_enter(&rtlsp->rtls_tx_lock); rtlsp->rtls_suspended = B_TRUE; rtls_chip_stop(rtlsp); mutex_exit(&rtlsp->rtls_tx_lock); mutex_exit(&rtlsp->rtls_rx_lock); mutex_exit(&rtlsp->rtls_io_lock); return (DDI_SUCCESS); default: return (DDI_FAILURE); } if (mac_unregister(rtlsp->mh) != 0) { /* device busy */ return (DDI_FAILURE); } ddi_periodic_delete(rtlsp->periodic_id); ddi_remove_intr(devinfo, 0, rtlsp->iblk); ddi_remove_softintr(rtlsp->resched_id); ddi_remove_softintr(rtlsp->events_id); mutex_destroy(&rtlsp->rtls_io_lock); mutex_destroy(&rtlsp->rtls_tx_lock); mutex_destroy(&rtlsp->rtls_rx_lock); ddi_regs_map_free(&rtlsp->io_handle); rtls_free_bufs(rtlsp); kmem_free(rtlsp, sizeof (rtls_t)); return (DDI_SUCCESS); } /* * quiesce(9E) entry point. * * This function is called when the system is single-threaded at high * PIL with preemption disabled. Therefore, this function must not be * blocked. * * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure. * DDI_FAILURE indicates an error condition and should almost never happen. */ static int rtls_quiesce(dev_info_t *devinfo) { rtls_t *rtlsp; /* our private device info */ /* * Get the driver private structure */ if ((rtlsp = ddi_get_driver_private(devinfo)) == NULL) { return (DDI_FAILURE); } return (rtls_chip_reset(rtlsp, B_TRUE)); } /* * ========== MAC Entry Points ========== */ /* * rtls_m_start() -- start the board receiving and allow transmits */ static int rtls_m_start(void *arg) { rtls_t *rtlsp = (rtls_t *)arg; mutex_enter(&rtlsp->rtls_io_lock); mutex_enter(&rtlsp->rtls_rx_lock); mutex_enter(&rtlsp->rtls_tx_lock); if (!rtlsp->rtls_suspended) rtls_chip_restart(rtlsp); rtlsp->rtls_running = B_TRUE; mutex_exit(&rtlsp->rtls_tx_lock); mutex_exit(&rtlsp->rtls_rx_lock); mutex_exit(&rtlsp->rtls_io_lock); return (0); } /* * rtls_m_stop() -- stop board receiving and transmits */ static void rtls_m_stop(void *arg) { rtls_t *rtlsp = (rtls_t *)arg; mutex_enter(&rtlsp->rtls_io_lock); if (!rtlsp->rtls_suspended) rtls_chip_stop(rtlsp); rtlsp->rtls_running = B_FALSE; mutex_exit(&rtlsp->rtls_io_lock); } /* * rtls_m_unicst() -- set the physical network address * on the board */ static int rtls_m_unicst(void *arg, const uint8_t *macaddr) { rtls_t *rtlsp = arg; mutex_enter(&rtlsp->rtls_io_lock); bcopy(macaddr, rtlsp->netaddr, ETHERADDRL); if (!rtlsp->rtls_suspended) rtls_set_mac_addr(rtlsp, rtlsp->netaddr); mutex_exit(&rtlsp->rtls_io_lock); return (0); } /* * rtls_m_multicst() -- set(enable) or disable a multicast address * * Program the hardware to enable/disable the multicast address in "mcast". */ static int rtls_m_multicst(void *arg, boolean_t enable, const uint8_t *mcast) { rtls_t *rtlsp = (rtls_t *)arg; uint_t index; uint32_t *hashp; mutex_enter(&rtlsp->rtls_io_lock); hashp = rtlsp->multi_hash; index = rtls_hash_index(mcast); /* index value is between 0 and 63 */ #ifdef RTLS_DEBUG if (rtls_debug & RTLS_REGCFG) { cmn_err(CE_NOTE, "%s: rtls_m_multicst: enable = %d, multi(%s), index=%d", rtlsp->ifname, enable, ether_sprintf((void *)mcast), index); } #endif if (enable) { if (rtlsp->multicast_cnt[index]++) { mutex_exit(&rtlsp->rtls_io_lock); return (0); } hashp[index/32] |= 1<< (index % 32); } else { if (--rtlsp->multicast_cnt[index]) { mutex_exit(&rtlsp->rtls_io_lock); return (0); } hashp[index/32] &= ~(1<< (index % 32)); } /* * Set multicast register */ if (!rtlsp->rtls_suspended) { rtls_reg_set32(rtlsp, MULTICAST_0_REG, hashp[0]); rtls_reg_set32(rtlsp, MULTICAST_4_REG, hashp[1]); } #ifdef RTLS_DEBUG if (rtls_debug & RTLS_REGCFG) { cmn_err(CE_NOTE, "%s: rtls_m_multicst =>: hash0=0x%x, hash1=0x%x", rtlsp->ifname, hashp[0], hashp[1]); cmn_err(CE_NOTE, "%s: rtls_m_multicst <=, hash0=0x%x, hash1=0x%x", rtlsp->ifname, rtls_reg_get32(rtlsp, MULTICAST_0_REG), rtls_reg_get32(rtlsp, MULTICAST_4_REG)); } #endif mutex_exit(&rtlsp->rtls_io_lock); return (0); } /* * rtls_hash_index() -- a hashing function used for setting the * node address or a multicast address */ static uint_t rtls_hash_index(const uint8_t *address) { uint32_t crc = (ulong_t)RTLS_HASH_CRC; uint32_t const POLY = RTLS_HASH_POLY; uint32_t msb; int bytes; uchar_t currentbyte; uint_t index; int bit; for (bytes = 0; bytes < ETHERADDRL; bytes++) { currentbyte = address[bytes]; for (bit = 0; bit < 8; bit++) { msb = crc >> 31; crc <<= 1; if (msb ^ (currentbyte & 1)) { crc ^= POLY; crc |= 0x00000001; } currentbyte >>= 1; } } index = crc >> 26; return (index); } /* * rtls_m_promisc() -- set or reset promiscuous mode on the board */ static int rtls_m_promisc(void *arg, boolean_t on) { rtls_t *rtlsp = arg; #ifdef RTLS_DEBUG if (rtls_debug & RTLS_REGCFG) { cmn_err(CE_NOTE, "%s: rtls_m_promisc: on=%d", rtlsp->ifname, on); } #endif mutex_enter(&rtlsp->rtls_io_lock); rtlsp->promisc = on; if (!rtlsp->rtls_suspended) { uint32_t val32 = rtls_reg_get32(rtlsp, RX_CONFIG_REG); if (on) { val32 |= RX_ACCEPT_ALL_PACKET; } else { val32 &= ~RX_ACCEPT_ALL_PACKET; } rtls_reg_set32(rtlsp, RX_CONFIG_REG, val32); } mutex_exit(&rtlsp->rtls_io_lock); return (0); } /* * rtls_m_stat() -- retrieve statistic * * MAC calls this routine just before it reads the driver's statistics * structure. If your board maintains statistics, this is the time to * read them in and update the values in the structure. If the driver * maintains statistics continuously, this routine need do nothing. */ static int rtls_m_stat(void *arg, uint_t stat, uint64_t *val) { rtls_t *rtlsp = arg; uint16_t bmcr, bmsr, anar, anlpar, aner; mutex_enter(&rtlsp->rtls_io_lock); if (!rtlsp->rtls_suspended) { bmcr = rtls_reg_get16(rtlsp, BASIC_MODE_CONTROL_REG); bmsr = rtls_reg_get16(rtlsp, BASIC_MODE_STATUS_REG); anar = rtls_reg_get16(rtlsp, AUTO_NEGO_AD_REG); anlpar = rtls_reg_get16(rtlsp, AUTO_NEGO_LP_REG); aner = rtls_reg_get16(rtlsp, AUTO_NEGO_EXP_REG); } else { bmcr = bmsr = anar = anlpar = aner = 0; } mutex_exit(&rtlsp->rtls_io_lock); switch (stat) { case MAC_STAT_IFSPEED: *val = rtlsp->stats.speed; break; case MAC_STAT_IPACKETS: *val = rtlsp->stats.ipackets; break; case MAC_STAT_RBYTES: *val = rtlsp->stats.rbytes; break; case MAC_STAT_OPACKETS: *val = rtlsp->stats.opackets; break; case MAC_STAT_OBYTES: *val = rtlsp->stats.obytes; break; case MAC_STAT_IERRORS: *val = rtlsp->stats.rcv_err; break; case MAC_STAT_OERRORS: *val = rtlsp->stats.xmt_err; break; case MAC_STAT_MULTIRCV: *val = rtlsp->stats.multi_rcv; break; case MAC_STAT_BRDCSTRCV: *val = rtlsp->stats.brdcst_rcv; break; case MAC_STAT_MULTIXMT: *val = rtlsp->stats.multi_xmt; break; case MAC_STAT_BRDCSTXMT: *val = rtlsp->stats.brdcst_xmt; break; case MAC_STAT_UNDERFLOWS: *val = rtlsp->stats.underflow; break; case MAC_STAT_OVERFLOWS: *val = rtlsp->stats.overflow; break; case MAC_STAT_NORCVBUF: *val = rtlsp->stats.no_rcvbuf; break; case MAC_STAT_COLLISIONS: *val = rtlsp->stats.collisions; break; case ETHER_STAT_LINK_DUPLEX: *val = rtlsp->stats.duplex; break; case ETHER_STAT_FCS_ERRORS: *val = rtlsp->stats.crc_err; break; case ETHER_STAT_ALIGN_ERRORS: *val = rtlsp->stats.frame_err; break; case ETHER_STAT_DEFER_XMTS: *val = rtlsp->stats.defer; break; case ETHER_STAT_TX_LATE_COLLISIONS: *val = rtlsp->stats.xmt_latecoll; break; case ETHER_STAT_TOOLONG_ERRORS: *val = rtlsp->stats.too_long; break; case ETHER_STAT_TOOSHORT_ERRORS: *val = rtlsp->stats.in_short; break; case ETHER_STAT_CARRIER_ERRORS: *val = rtlsp->stats.no_carrier; break; case ETHER_STAT_FIRST_COLLISIONS: *val = rtlsp->stats.firstcol; break; case ETHER_STAT_MULTI_COLLISIONS: *val = rtlsp->stats.multicol; break; case ETHER_STAT_CAP_100FDX: case ETHER_STAT_CAP_100HDX: case ETHER_STAT_CAP_10FDX: case ETHER_STAT_CAP_10HDX: case ETHER_STAT_CAP_AUTONEG: *val = 1; /* these are all true by default */ break; case ETHER_STAT_ADV_CAP_100FDX: *val = anar & AUTO_NEGO_100FULL ? 1 : 0; break; case ETHER_STAT_ADV_CAP_100HDX: *val = anar & AUTO_NEGO_100HALF ? 1 : 0; break; case ETHER_STAT_ADV_CAP_10FDX: *val = anar & AUTO_NEGO_10FULL ? 1 : 0; break; case ETHER_STAT_ADV_CAP_10HDX: *val = anar & AUTO_NEGO_10HALF ? 1 : 0; break; case ETHER_STAT_ADV_CAP_AUTONEG: *val = bmcr & BASIC_MODE_AUTONEGO ? 1 : 0; break; case ETHER_STAT_LP_CAP_100FDX: *val = anlpar & AUTO_NEGO_100FULL ? 1 : 0; break; case ETHER_STAT_LP_CAP_100HDX: *val = anlpar & AUTO_NEGO_100HALF ? 1 : 0; break; case ETHER_STAT_LP_CAP_10FDX: *val = anlpar & AUTO_NEGO_10FULL ? 1 : 0; break; case ETHER_STAT_LP_CAP_10HDX: *val = anlpar & AUTO_NEGO_10HALF ? 1 : 0; break; case ETHER_STAT_LP_CAP_AUTONEG: *val = aner & AUTO_NEGO_EXP_LPCANAN ? 1 : 0; break; case ETHER_STAT_LINK_AUTONEG: *val = ((aner & AUTO_NEGO_EXP_LPCANAN) && (bmcr & BASIC_MODE_AUTONEGO) && (bmsr & BASIC_MODE_STATUS_AUTONEGO_DONE)) ? 1 : 0; break; default: return (ENOTSUP); } /* * RTL8139 don't support MII statistics, * these values are maintained by the driver software. */ #ifdef RTLS_DEBUG if (rtls_debug & RTLS_TRACE) rtls_reg_print(rtlsp); #endif return (0); } /* * rtls_send() -- send a packet * * Called when a packet is ready to be transmitted. A pointer to an * M_DATA message that contains the packet is passed to this routine. * The complete LLC header is contained in the message's first message * block, and the remainder of the packet is contained within * additional M_DATA message blocks linked to the first message block. * * Returns B_TRUE if the packet was properly disposed of, or B_FALSE if * if the packet is being deferred and should be tried again later. */ static boolean_t rtls_send(rtls_t *rtlsp, mblk_t *mp) { int totlen; int ncc; uint16_t cur_desc; uint32_t tx_status; ASSERT(mp != NULL); ASSERT(rtlsp->rtls_running); mutex_enter(&rtlsp->rtls_tx_lock); if (rtlsp->rtls_suspended) { mutex_exit(&rtlsp->rtls_tx_lock); return (B_FALSE); } /* * If chip error ... */ if (rtlsp->chip_error) { #ifdef RTLS_DEBUG cmn_err(CE_WARN, "%s: send fail--CHIP ERROR!", rtlsp->ifname); #endif mutex_exit(&rtlsp->rtls_tx_lock); freemsg(mp); return (B_TRUE); } /* * If chip link down ... */ if (rtlsp->link_state == LINK_STATE_DOWN) { #ifdef RTLS_DEBUG cmn_err(CE_WARN, "%s: send fail--LINK DOWN!", rtlsp->ifname); #endif rtlsp->stats.no_carrier++; mutex_exit(&rtlsp->rtls_tx_lock); freemsg(mp); return (B_TRUE); } /* * Current transmit descriptor */ cur_desc = rtlsp->tx_current_desc; ASSERT(cur_desc < RTLS_MAX_TX_DESC); /* * RealTek 8139 has 4 tx descriptor for transmit. In the first tx loop * of transmit,we needn't judge transmit status. */ if (rtlsp->tx_first_loop < RTLS_MAX_TX_DESC) { rtlsp->tx_first_loop++; goto tx_ready; } /* * If it's not the first tx loop, we need judge whether the chip is * busy or not. Otherwise, we have to reschedule send and wait... */ tx_status = rtls_reg_get32(rtlsp, TX_STATUS_DESC0_REG + 4 * cur_desc); /* * H/W doesn't complete packet transmit */ if (!(tx_status & TX_COMPLETE_FLAG)) { #ifdef RTLS_DEBUG if (rtls_debug & RTLS_SEND) { cmn_err(CE_NOTE, "%s: rtls_send: need_sched", rtlsp->ifname); } #endif /* * Through test, we find RTL8139 tx status might be * not-completing all along. We have to reset chip * to make RTL8139 tansmit re-work. */ if (rtlsp->tx_retry++ > RTLS_TX_RETRY_NUM) { /* * Wait transmit h/w more time... */ RTLS_TX_WAIT_TIMEOUT; /* 100 ms */ /* * Judge tx status again, if it remains not-completing, * we can confirm RTL8139 is in chip error state * and must reset it. */ tx_status = rtls_reg_get32(rtlsp, TX_STATUS_DESC0_REG + 4 * cur_desc); if (!(tx_status & TX_COMPLETE_FLAG)) { #ifdef RTLS_DEBUG cmn_err(CE_NOTE, "%s: tx chip_error = 0x%x", rtlsp->ifname, tx_status); #endif rtlsp->tx_retry = 0; rtlsp->chip_error = B_TRUE; rtlsp->stats.xmt_err++; rtlsp->stats.mac_xmt_err++; mutex_exit(&rtlsp->rtls_tx_lock); freemsg(mp); return (B_TRUE); } } else { rtlsp->stats.defer++; rtlsp->need_sched = B_TRUE; mutex_exit(&rtlsp->rtls_tx_lock); return (B_FALSE); } } /* * Transmit error? */ if (tx_status & TX_ERR_FLAG) { #ifdef RTLS_DEBUG if (rtls_debug & RTLS_SEND) { cmn_err(CE_NOTE, "%s: transmit error, status = 0x%x", rtlsp->ifname, tx_status); } #endif rtlsp->stats.xmt_err++; if (tx_status & TX_STATUS_TX_UNDERRUN) rtlsp->stats.underflow++; if (tx_status & TX_STATUS_CS_LOST) rtlsp->stats.no_carrier++; if (tx_status & TX_STATUS_OWC) rtlsp->stats.xmt_latecoll++; } ncc = ((tx_status & TX_STATUS_NCC) >> TX_STATUS_NCC_SHIFT); if (ncc != 0) { rtlsp->stats.collisions += ncc; rtlsp->stats.firstcol++; rtlsp->stats.multicol += ncc - 1; } tx_ready: /* * Initialize variable */ rtlsp->tx_retry = 0; totlen = 0; /* * Copy packet to tx descriptor buffer */ totlen = msgsize(mp); if (totlen > (ETHERMAX + 4)) { /* 4 bytes for VLAN header */ cmn_err(CE_NOTE, "%s: rtls_send: try to send large %d packet", rtlsp->ifname, totlen); rtlsp->stats.mac_xmt_err++; rtlsp->stats.xmt_err++; freemsg(mp); mutex_exit(&rtlsp->rtls_tx_lock); return (B_TRUE); } /* this will free the mblk */ mcopymsg(mp, rtlsp->tx_buf[cur_desc]); /* update stats */ if (*rtlsp->tx_buf[cur_desc] & 0x1) { uint16_t *ptr = (void *)rtlsp->tx_buf[cur_desc]; if ((ptr[0] == 0xffff) && (ptr[1] == 0xffff) && (ptr[2] == 0xffff)) { rtlsp->stats.brdcst_xmt++; } else { rtlsp->stats.multi_xmt++; } } rtlsp->stats.opackets++; rtlsp->stats.obytes += totlen; if (totlen < ETHERMIN) { bzero(rtlsp->tx_buf[cur_desc] + totlen, ETHERMIN - totlen); totlen = ETHERMIN; } /* make sure caches are flushed */ (void) ddi_dma_sync(rtlsp->dma_area_tx[cur_desc].dma_hdl, 0, totlen, DDI_DMA_SYNC_FORDEV); /* * Start transmit * set transmit FIFO threshhold to 0x30*32 = 1536 bytes * to avoid tx underrun. */ rtls_reg_set32(rtlsp, TX_STATUS_DESC0_REG + 4 * cur_desc, totlen | (0x30 << TX_STATUS_TX_THRESHOLD_SHIFT)); /* * Update the value of current tx descriptor */ cur_desc++; cur_desc %= RTLS_MAX_TX_DESC; rtlsp->tx_current_desc = cur_desc; mutex_exit(&rtlsp->rtls_tx_lock); return (B_TRUE); } /* * rtls_m_tx() -- send a chain of packets, linked by mp->b_next. */ static mblk_t * rtls_m_tx(void *arg, mblk_t *mp) { rtls_t *rtlsp = arg; mblk_t *next; while (mp != NULL) { next = mp->b_next; mp->b_next = NULL; if (!rtls_send(rtlsp, mp)) { mp->b_next = next; break; } mp = next; } return (mp); } /* * rtls_receive() -- receive packets * * Called when receive interrupts detected */ static void rtls_receive(rtls_t *rtlsp) { mblk_t *head = NULL; mblk_t **mpp; mblk_t *mp; uint16_t rx_status; uint16_t packet_len; int wrap_size; uint32_t cur_rx; uint8_t *rx_ptr; mpp = &head; mutex_enter(&rtlsp->rtls_rx_lock); if (rtlsp->rtls_suspended) { mutex_exit(&rtlsp->rtls_rx_lock); return; } while ((rtls_reg_get8(rtlsp, RT_COMMAND_REG) & RT_COMMAND_BUFF_EMPTY) == 0) { /* * Chip error state */ if (rtlsp->chip_error) { #ifdef RTLS_DEBUG cmn_err(CE_WARN, "%s: receive fail--CHIP ERROR!", rtlsp->ifname); #endif break; } cur_rx = rtlsp->cur_rx; rx_ptr = rtlsp->rx_ring + cur_rx; packet_len = (rx_ptr[3] << 8) | (rx_ptr[2]); rx_status = rx_ptr[0]; /* * DMA still in progress */ if (packet_len == RX_STATUS_DMA_BUSY) { cmn_err(CE_NOTE, "%s: Rx DMA still in progress", rtlsp->ifname); break; } /* * Check receive status */ if ((rx_status & RX_ERR_FLAGS) || (!(rx_status & RX_HEADER_STATUS_ROK)) || (packet_len < (ETHERMIN + ETHERFCSL)) || (packet_len > (ETHERMAX + ETHERFCSL + 4))) { #ifdef RTLS_DEBUG cmn_err(CE_NOTE, "%s: receive error, status = 0x%x, length = %d", rtlsp->ifname, rx_status, packet_len); #endif /* * Rx error statistics */ if ((rx_status & RX_HEADER_STATUS_RUNT) || (packet_len < (ETHERMIN + ETHERFCSL))) rtlsp->stats.in_short++; else if (packet_len > (ETHERMAX + ETHERFCSL + 4)) rtlsp->stats.too_long++; else if (rx_status & RX_HEADER_STATUS_CRC) rtlsp->stats.crc_err++; else if (rx_status & RX_HEADER_STATUS_FAE) rtlsp->stats.frame_err++; /* * Set chip_error flag to reset chip: * (suggested in RealTek programming guide.) */ rtlsp->chip_error = B_TRUE; mutex_exit(&rtlsp->rtls_rx_lock); return; } /* * We need not up-send ETHERFCSL bytes of receive packet */ packet_len -= ETHERFCSL; /* * Allocate buffer to receive this good packet */ mp = allocb(packet_len, 0); /* * Copy the data found into the new cluster, we have (+4) * to get us past the packet head data that the rtl chip * places at the start of the message */ if ((cur_rx + packet_len + RX_HEADER_SIZE) > RTLS_RX_BUF_RING) { wrap_size = cur_rx + packet_len + RX_HEADER_SIZE - RTLS_RX_BUF_RING; #ifdef RTLS_DEBUG if (rtls_debug & RTLS_RECV) { cmn_err(CE_NOTE, "%s: Rx: packet_len = %d, wrap_size = %d", rtlsp->ifname, packet_len, wrap_size); } #endif if (mp != NULL) { /* Flush caches */ (void) ddi_dma_sync(rtlsp->dma_area_rx.dma_hdl, cur_rx + RX_HEADER_SIZE, packet_len - wrap_size, DDI_DMA_SYNC_FORKERNEL); (void) ddi_dma_sync(rtlsp->dma_area_rx.dma_hdl, 0, wrap_size, DDI_DMA_SYNC_FORKERNEL); /* * Copy in first section of message as stored * at the end of the ring buffer */ bcopy(rx_ptr + RX_HEADER_SIZE, mp->b_wptr, packet_len - wrap_size); mp->b_wptr += packet_len - wrap_size; bcopy(rtlsp->rx_ring, mp->b_wptr, wrap_size); mp->b_wptr += wrap_size; *mpp = mp; mpp = &mp->b_next; rtlsp->stats.ipackets++; if (rx_status & RX_HEADER_STATUS_BCAST) rtlsp->stats.brdcst_rcv++; if (rx_status & RX_HEADER_STATUS_MULTI) rtlsp->stats.multi_rcv++; rtlsp->stats.rbytes += packet_len; } else { rtlsp->stats.no_rcvbuf++; } cur_rx = RTLS_RX_ADDR_ALIGNED(wrap_size + ETHERFCSL); /* 4-byte aligned */ } else { if (mp != NULL) { /* Flush caches */ (void) ddi_dma_sync(rtlsp->dma_area_rx.dma_hdl, cur_rx + RX_HEADER_SIZE, packet_len, DDI_DMA_SYNC_FORKERNEL); bcopy(rx_ptr + RX_HEADER_SIZE, mp->b_wptr, packet_len); mp->b_wptr += packet_len; *mpp = mp; mpp = &mp->b_next; rtlsp->stats.ipackets++; if (rx_status & RX_HEADER_STATUS_BCAST) rtlsp->stats.brdcst_rcv++; if (rx_status & RX_HEADER_STATUS_MULTI) rtlsp->stats.multi_rcv++; rtlsp->stats.rbytes += packet_len; } else { rtlsp->stats.no_rcvbuf++; } cur_rx += packet_len + RX_HEADER_SIZE + ETHERFCSL; cur_rx = RTLS_RX_ADDR_ALIGNED(cur_rx); /* 4-byte aligned */ } /* * Update rx buffer ring read pointer: * give us a little leeway to ensure no overflow */ rtlsp->cur_rx = cur_rx; rtls_reg_set16(rtlsp, RX_CURRENT_READ_ADDR_REG, cur_rx - READ_ADDR_GAP); } mutex_exit(&rtlsp->rtls_rx_lock); /* * Upsend packet */ if (head) { mac_rx(rtlsp->mh, NULL, head); } } /* * rtls_intr() -- interrupt from board to inform us that a receive or * link change. */ static uint_t rtls_intr(caddr_t arg) { rtls_t *rtlsp = (void *)arg; uint32_t int_status; uint8_t media_status; int32_t link; uint32_t val32; mutex_enter(&rtlsp->rtls_io_lock); /* * Was this interrupt caused by our device... */ int_status = rtls_reg_get16(rtlsp, RT_INT_STATUS_REG); if (!(int_status & rtlsp->int_mask)) { mutex_exit(&rtlsp->rtls_io_lock); return (DDI_INTR_UNCLAIMED); /* indicate it wasn't our interrupt */ } /* * Clear interrupt */ rtls_reg_set16(rtlsp, RT_INT_STATUS_REG, int_status); /* * If chip error, restart chip... */ if (rtlsp->chip_error) { mutex_enter(&rtlsp->rtls_rx_lock); mutex_enter(&rtlsp->rtls_tx_lock); rtls_chip_restart(rtlsp); rtlsp->chip_error = B_FALSE; rtlsp->tx_retry = 0; mutex_exit(&rtlsp->rtls_tx_lock); mutex_exit(&rtlsp->rtls_rx_lock); mutex_exit(&rtlsp->rtls_io_lock); return (DDI_INTR_CLAIMED); /* no need to hand other interrupts */ } /* * Transmit error interrupt */ if (int_status & TX_ERR_INT) { val32 = rtls_reg_get32(rtlsp, TX_CONFIG_REG); val32 |= TX_CLEAR_ABORT; rtls_reg_set32(rtlsp, TX_CONFIG_REG, val32); cmn_err(CE_WARN, "%s: transmit abort!!!", rtlsp->ifname); } /* * Cable link change interrupt */ if (int_status & LINK_CHANGE_INT) { media_status = rtls_reg_get8(rtlsp, MEDIA_STATUS_REG); link = (media_status & MEDIA_STATUS_LINK) ? LINK_STATE_DOWN : LINK_STATE_UP; if (rtlsp->link_state != link) { rtlsp->link_state = link; rtlsp->link_change = B_TRUE; /* * Trigger rtls_events */ ddi_trigger_softintr(rtlsp->events_id); } } /* * Trigger mac_tx_update */ if (rtlsp->need_sched && !rtlsp->sched_running) { rtlsp->sched_running = B_TRUE; ddi_trigger_softintr(rtlsp->resched_id); } mutex_exit(&rtlsp->rtls_io_lock); /* * Receive interrupt */ if (int_status & RTLS_RX_INT) { if (int_status & RX_OVERFLOW_INT) { rtlsp->stats.overflow++; rtlsp->stats.rcv_err++; } rtls_receive(rtlsp); } return (DDI_INTR_CLAIMED); /* indicate it was our interrupt */ } static void rtls_periodic(void *arg) { rtls_t *rtlsp = (void *)arg; uint32_t link; uint8_t media_status; mutex_enter(&rtlsp->rtls_io_lock); if (rtlsp->rtls_suspended) { mutex_exit(&rtlsp->rtls_io_lock); return; } media_status = rtls_reg_get8(rtlsp, MEDIA_STATUS_REG); link = (media_status & MEDIA_STATUS_LINK) ? LINK_STATE_DOWN : LINK_STATE_UP; if (rtlsp->link_state != link) { rtlsp->link_state = link; rtlsp->link_change = B_TRUE; /* * Trigger rtls_events */ ddi_trigger_softintr(rtlsp->events_id); } mutex_exit(&rtlsp->rtls_io_lock); } /* * rtls_reschedule() -- soft interrupt handler */ static uint_t rtls_reschedule(caddr_t arg) { rtls_t *rtlsp = (void *)arg; uint_t tmp; tmp = DDI_INTR_UNCLAIMED; if (rtlsp->need_sched && rtlsp->rtls_running) { #ifdef RTLS_DEBUG if (rtls_debug & RTLS_SEND) { cmn_err(CE_NOTE, "%s: rtls_reschedule", rtlsp->ifname); } #endif rtlsp->need_sched = B_FALSE; mac_tx_update(rtlsp->mh); rtlsp->sched_running = B_FALSE; tmp = DDI_INTR_CLAIMED; } return (tmp); } /* * rtls_events() -- soft interrupt handler * * The rtls_events is woken up when there's something to do that we'd rather * not do from inside a hardware interrupt handler. * Its main task now is just: * notice mac the link status changed */ static uint_t rtls_events(caddr_t arg) { rtls_t *rtlsp = (void *)arg; uint_t tmp = DDI_INTR_UNCLAIMED; uint8_t media_status; uint16_t duplex_mode; /* * If the link state changed, tell the world about it. * Note: can't do this while still holding the mutex. */ if (rtlsp->link_change) { rtlsp->link_change = B_FALSE; if (rtlsp->link_state == LINK_STATE_UP) { media_status = rtls_reg_get8(rtlsp, MEDIA_STATUS_REG); rtlsp->stats.speed = (media_status & MEDIA_STATUS_SPEED) ? RTLS_SPEED_10M : RTLS_SPEED_100M; duplex_mode = rtls_reg_get16(rtlsp, BASIC_MODE_CONTROL_REG); rtlsp->stats.duplex = (duplex_mode & BASIC_MODE_DUPLEX) ? LINK_DUPLEX_FULL : LINK_DUPLEX_HALF; } mac_link_update(rtlsp->mh, rtlsp->link_state); tmp = DDI_INTR_CLAIMED; } return (tmp); } /* * ========== Buffer Management Routines ========== */ /* * rtls_alloc_dma_mem() -- allocate an area of memory and a DMA handle * for accessing it */ static int rtls_alloc_dma_mem(rtls_t *rtlsp, size_t memsize, ddi_device_acc_attr_t *attr_p, uint_t dma_flags, dma_area_t *dma_p) { caddr_t vaddr; int err; /* * Allocate handle */ err = ddi_dma_alloc_handle(rtlsp->devinfo, &dma_attr, DDI_DMA_SLEEP, NULL, &dma_p->dma_hdl); if (err != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: rtls_alloc_dma_mem: ddi_dma_alloc_handle failed: %d", rtlsp->ifname, err); dma_p->dma_hdl = NULL; return (DDI_FAILURE); } /* * Allocate memory */ err = ddi_dma_mem_alloc(dma_p->dma_hdl, memsize, attr_p, dma_flags & (DDI_DMA_CONSISTENT | DDI_DMA_STREAMING), DDI_DMA_SLEEP, NULL, &vaddr, &dma_p->alength, &dma_p->acc_hdl); if (err != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: rtls_alloc_dma_mem: ddi_dma_mem_alloc failed: %d", rtlsp->ifname, err); ddi_dma_free_handle(&dma_p->dma_hdl); dma_p->dma_hdl = NULL; dma_p->acc_hdl = NULL; return (DDI_FAILURE); } /* * Bind the two together */ dma_p->mem_va = vaddr; err = ddi_dma_addr_bind_handle(dma_p->dma_hdl, NULL, vaddr, dma_p->alength, dma_flags, DDI_DMA_SLEEP, NULL, &dma_p->cookie, &dma_p->ncookies); if (err != DDI_DMA_MAPPED || dma_p->ncookies != 1) { cmn_err(CE_WARN, "%s: rtls_alloc_dma_mem: " "ddi_dma_addr_bind_handle failed: %d", rtlsp->ifname, err); ddi_dma_mem_free(&dma_p->acc_hdl); ddi_dma_free_handle(&dma_p->dma_hdl); dma_p->acc_hdl = NULL; dma_p->dma_hdl = NULL; return (DDI_FAILURE); } return (DDI_SUCCESS); } /* * rtls_free_dma_mem() -- free one allocated area of DMAable memory */ static void rtls_free_dma_mem(dma_area_t *dma_p) { if (dma_p->dma_hdl != NULL) { if (dma_p->ncookies) { (void) ddi_dma_unbind_handle(dma_p->dma_hdl); dma_p->ncookies = 0; } ddi_dma_free_handle(&dma_p->dma_hdl); dma_p->dma_hdl = NULL; } if (dma_p->acc_hdl != NULL) { ddi_dma_mem_free(&dma_p->acc_hdl); dma_p->acc_hdl = NULL; } } /* * rtls_alloc_bufs() -- allocate descriptors/buffers for this device instance */ static int rtls_alloc_bufs(rtls_t *rtlsp) { int i; int err; /* * Allocate memory & handle for Tx buffers */ for (i = 0; i < RTLS_MAX_TX_DESC; i++) { err = rtls_alloc_dma_mem(rtlsp, RTLS_TX_BUF_SIZE, &rtls_buf_accattr, DDI_DMA_WRITE | DDI_DMA_STREAMING, &rtlsp->dma_area_tx[i]); if (err != DDI_SUCCESS) return (DDI_FAILURE); rtlsp->tx_buf[i] = (uint8_t *)rtlsp->dma_area_tx[i].mem_va; } /* * Allocate memory & handle for Rx buffers */ err = rtls_alloc_dma_mem(rtlsp, RTLS_RX_BUF_SIZE, &rtls_buf_accattr, DDI_DMA_READ | DDI_DMA_STREAMING, &rtlsp->dma_area_rx); if (err != DDI_SUCCESS) return (DDI_FAILURE); rtlsp->rx_ring = (uint8_t *)rtlsp->dma_area_rx.mem_va; return (DDI_SUCCESS); } /* * rtls_free_bufs() -- free descriptors/buffers allocated for this * device instance. */ static void rtls_free_bufs(rtls_t *rtlsp) { int i; for (i = 0; i < RTLS_MAX_TX_DESC; i++) { rtls_free_dma_mem(&rtlsp->dma_area_tx[i]); rtlsp->tx_buf[i] = NULL; } rtls_free_dma_mem(&rtlsp->dma_area_rx); rtlsp->rx_ring = NULL; } /* * ========== Chip H/W Operation Routines ========== */ /* * rtls_chip_reset() -- reset chip */ static int rtls_chip_reset(rtls_t *rtlsp, boolean_t quiesce) { int i; uint16_t val16; uint8_t val8; /* * Chip should be in STOP state */ val8 = rtls_reg_get8(rtlsp, RT_COMMAND_REG); val8 &= ~(RT_COMMAND_RX_ENABLE | RT_COMMAND_TX_ENABLE); rtls_reg_set8(rtlsp, RT_COMMAND_REG, val8); /* * Disable interrupt */ val16 = rtls_reg_get16(rtlsp, RT_INT_MASK_REG); rtls_reg_set16(rtlsp, RT_INT_MASK_REG, val16 & (~RTLS_INT_MASK_ALL)); rtlsp->int_mask = RTLS_INT_MASK_NONE; /* * Clear pended interrupt */ val16 = rtls_reg_get16(rtlsp, RT_INT_STATUS_REG); rtls_reg_set16(rtlsp, RT_INT_STATUS_REG, val16); /* * Reset chip */ val8 = rtls_reg_get8(rtlsp, RT_COMMAND_REG); rtls_reg_set8(rtlsp, RT_COMMAND_REG, val8 | RT_COMMAND_RESET); /* * Wait for reset success */ i = 0; while (rtls_reg_get8(rtlsp, RT_COMMAND_REG) & RT_COMMAND_RESET) { if (++i > RTLS_RESET_WAIT_NUM) { /* * At quiesce path we can't call cmn_err(), as * it might block */ if (!quiesce) cmn_err(CE_WARN, "%s: chip reset fail.", rtlsp->ifname); return (DDI_FAILURE); } RTLS_RESET_WAIT_INTERVAL; } return (DDI_SUCCESS); } /* * rtls_chip_init() -- initialize the specified network board short of * actually starting the board. Call after rtls_chip_reset(). */ static void rtls_chip_init(rtls_t *rtlsp) { static boolean_t first_time = B_TRUE; int *forced_speed_and_duplex; uint_t item_num; uint32_t val32; uint16_t val16; uint8_t val8; int err; int i; /* * Initialize internal data structures */ rtlsp->cur_rx = 0; rtlsp->tx_current_desc = 0; rtlsp->tx_first_loop = 0; /* * Read ForceSpeedDuplex from rtls.conf file */ if (first_time) { first_time = B_FALSE; /* * Set ForceSpeedDuplex array default value * to 5:Anto-Negotiation */ for (i = 0; i < RTLS_MAX_DEVICES; i++) rtls_link_mode[i] = FORCE_AUTO_NEGO; /* Read ForceSpeedDuplex value */ err = ddi_prop_lookup_int_array( DDI_DEV_T_ANY, rtlsp->devinfo, DDI_PROP_DONTPASS, "ForceSpeedDuplex", &forced_speed_and_duplex, &item_num); if (err == DDI_PROP_SUCCESS) { for (i = 0; i <= item_num; i++) { rtls_link_mode[i] = forced_speed_and_duplex[i]; } ddi_prop_free(forced_speed_and_duplex); } #ifdef RTLS_DEBUG if (err == DDI_PROP_SUCCESS) { for (i = 0; i < item_num; i++) cmn_err(CE_NOTE, "rtls.conf: rtls%d, ForceSpeedDuplex = %d", i, rtls_link_mode[i]); } else { cmn_err(CE_NOTE, "rtls: read ForceSpeedDuplex in rtls.conf fail."); } #endif } rtlsp->force_speed_duplex = rtls_link_mode[rtlsp->instance]; /* * Set duplex and speed */ rtls_chip_force_speed_duplex(rtlsp); /* * Set DMA physical rx/tx buffer address to register */ rtls_reg_set32(rtlsp, RX_BUFF_ADDR_REG, (ulong_t)rtlsp->dma_area_rx.cookie.dmac_address); rtls_reg_set32(rtlsp, TX_ADDR_DESC0_REG, (ulong_t)rtlsp->dma_area_tx[0].cookie.dmac_address); rtls_reg_set32(rtlsp, TX_ADDR_DESC1_REG, (ulong_t)rtlsp->dma_area_tx[1].cookie.dmac_address); rtls_reg_set32(rtlsp, TX_ADDR_DESC2_REG, (ulong_t)rtlsp->dma_area_tx[2].cookie.dmac_address); rtls_reg_set32(rtlsp, TX_ADDR_DESC3_REG, (ulong_t)rtlsp->dma_area_tx[3].cookie.dmac_address); /* * Start transmit/receive before set tx/rx configuration register */ val8 = rtls_reg_get8(rtlsp, RT_COMMAND_REG); rtls_reg_set8(rtlsp, RT_COMMAND_REG, val8 | RT_COMMAND_RX_ENABLE | RT_COMMAND_TX_ENABLE); /* * Set transmit configuration register */ val32 = rtls_reg_get32(rtlsp, TX_CONFIG_REG); val32 &= TX_CONSIG_REG_RESERVE; rtls_reg_set32(rtlsp, TX_CONFIG_REG, val32 | TX_CONFIG_DEFAULT); /* * Set receive configuration register */ val32 = rtls_reg_get32(rtlsp, RX_CONFIG_REG); val32 &= RX_CONSIG_REG_RESERVE; if (rtlsp->promisc) val32 |= RX_ACCEPT_ALL_PACKET; rtls_reg_set32(rtlsp, RX_CONFIG_REG, val32 | RX_CONFIG_DEFAULT); /* * Set multicast register */ rtls_reg_set32(rtlsp, MULTICAST_0_REG, rtlsp->multi_hash[0]); rtls_reg_set32(rtlsp, MULTICAST_4_REG, rtlsp->multi_hash[1]); /* * Set unicast address */ rtls_set_mac_addr(rtlsp, rtlsp->netaddr); /* * Set current address of packet read */ rtls_reg_set16(rtlsp, RX_CURRENT_READ_ADDR_REG, RX_READ_RESET_VAL); /* * No early-rx interrupts */ val16 = rtls_reg_get16(rtlsp, RT_MUL_INTSEL_REG); val16 &= ~RT_MUL_INTSEL_BITS; rtls_reg_set16(rtlsp, RT_MUL_INTSEL_REG, val16); } /* * rtls_chip_start() -- start chip */ static void rtls_chip_start(rtls_t *rtlsp) { uint16_t val16; uint8_t val8; /* * Start transmit/receive */ val8 = rtls_reg_get8(rtlsp, RT_COMMAND_REG); rtls_reg_set8(rtlsp, RT_COMMAND_REG, val8 | RT_COMMAND_RX_ENABLE | RT_COMMAND_TX_ENABLE); /* * Enable interrupt */ val16 = rtls_reg_get16(rtlsp, RT_INT_MASK_REG); rtls_reg_set16(rtlsp, RT_INT_MASK_REG, val16 | RTLS_INT_MASK); rtlsp->int_mask = RTLS_INT_MASK; } /* * rtls_chip_restart() -- restart chip */ static void rtls_chip_restart(rtls_t *rtlsp) { (void) rtls_chip_reset(rtlsp, B_FALSE); rtls_chip_init(rtlsp); rtls_chip_start(rtlsp); } /* * rtls_chip_stop() -- stop board receiving */ static void rtls_chip_stop(rtls_t *rtlsp) { uint16_t val16; uint8_t val8; /* * Disable interrupt */ val16 = rtls_reg_get16(rtlsp, RT_INT_MASK_REG); rtls_reg_set16(rtlsp, RT_INT_MASK_REG, val16 & (~RTLS_INT_MASK_ALL)); rtlsp->int_mask = RTLS_INT_MASK_NONE; /* * Clear pended interrupt */ val16 = rtls_reg_get16(rtlsp, RT_INT_STATUS_REG); rtls_reg_set16(rtlsp, RT_INT_STATUS_REG, val16); /* * Stop the board and disable transmit/receive */ val8 = rtls_reg_get8(rtlsp, RT_COMMAND_REG); val8 &= ~(RT_COMMAND_RX_ENABLE | RT_COMMAND_TX_ENABLE); rtls_reg_set8(rtlsp, RT_COMMAND_REG, val8); } /* * rtls_get_mac_addr() -- get the physical network address on the board */ static void rtls_get_mac_addr(rtls_t *rtlsp, uint8_t *macaddr) { uint32_t val32; /* * Read first 4-byte of mac address */ val32 = rtls_reg_get32(rtlsp, ID_0_REG); macaddr[0] = val32 & 0xff; val32 = val32 >> 8; macaddr[1] = val32 & 0xff; val32 = val32 >> 8; macaddr[2] = val32 & 0xff; val32 = val32 >> 8; macaddr[3] = val32 & 0xff; /* * Read last 2-byte of mac address */ val32 = rtls_reg_get32(rtlsp, ID_4_REG); macaddr[4] = val32 & 0xff; val32 = val32 >> 8; macaddr[5] = val32 & 0xff; } static void rtls_set_mac_addr(rtls_t *rtlsp, const uint8_t *macaddr) { uint32_t val32; uint8_t val8; #ifdef RTLS_DEBUG if (rtls_debug & RTLS_REGCFG) { cmn_err(CE_NOTE, "%s:rtls_set_mac_addr(%s)", rtlsp->ifname, ether_sprintf((void *)macaddr)); } #endif /* * Change to config register write enable mode */ val8 = rtls_reg_get8(rtlsp, RT_93c46_COMMOND_REG); val8 |= RT_93c46_MODE_CONFIG; rtls_reg_set8(rtlsp, RT_93c46_COMMOND_REG, val8); /* * Get first 4 bytes of mac address */ val32 = macaddr[3]; val32 = val32 << 8; val32 |= macaddr[2]; val32 = val32 << 8; val32 |= macaddr[1]; val32 = val32 << 8; val32 |= macaddr[0]; /* * Set first 4 bytes of mac address */ rtls_reg_set32(rtlsp, ID_0_REG, val32); #ifdef RTLS_DEBUG if (rtls_debug & RTLS_REGCFG) { cmn_err(CE_NOTE, "%s: rtls_m_unicst 0 => 0x%x", rtlsp->ifname, val32); val32 = rtls_reg_get32(rtlsp, ID_0_REG); cmn_err(CE_NOTE, "%s: rtls_m_unicst 0 <= 0x%x", rtlsp->ifname, val32); } #endif /* * Get last 2 bytes of mac address */ val32 = macaddr[5]; val32 = val32 << 8; val32 |= macaddr[4]; /* * Set last 2 bytes of mac address */ val32 |= rtls_reg_get32(rtlsp, ID_4_REG) & ~0xffff; rtls_reg_set32(rtlsp, ID_4_REG, val32); #ifdef RTLS_DEBUG if (rtls_debug & RTLS_REGCFG) { cmn_err(CE_NOTE, "%s: rtls_m_unicst 1 => 0x%x", rtlsp->ifname, val32); val32 = rtls_reg_get32(rtlsp, ID_4_REG); cmn_err(CE_NOTE, "%s: rtls_m_unicst 1 <= 0x%x", rtlsp->ifname, val32); } #endif /* * Return to normal network/host communication mode */ val8 &= ~RT_93c46_MODE_CONFIG; rtls_reg_set8(rtlsp, RT_93c46_COMMOND_REG, val8); } /* * rtls_chip_force_speed_duplex() -- set chip speed and duplex mode */ static void rtls_chip_force_speed_duplex(rtls_t *rtlsp) { uint16_t control; uint16_t ad_mode; uint8_t val8; control = rtls_reg_get16(rtlsp, BASIC_MODE_CONTROL_REG); control &= ~BASIC_MODE_CONTROL_BITS; ad_mode = rtls_reg_get16(rtlsp, AUTO_NEGO_AD_REG); ad_mode &= ~AUTO_NEGO_MODE_BITS; #ifdef RTLS_DEBUG if (rtls_debug & RTLS_TRACE) { cmn_err(CE_NOTE, "%s:rtls_chip_force_speed_duplex = %d", rtlsp->ifname, rtlsp->force_speed_duplex); } #endif switch (rtlsp->force_speed_duplex) { default: cmn_err(CE_WARN, "%s: Bad ForceSpeedDuplex = %d value, " "will use its default value 5: Anto-Negotiation", rtlsp->ifname, rtlsp->force_speed_duplex); rtlsp->force_speed_duplex = FORCE_AUTO_NEGO; control |= BASIC_MODE_AUTONEGO | BASIC_MODE_RESTAR_AUTONEGO; ad_mode |= AUTO_NEGO_100FULL | AUTO_NEGO_100HALF | AUTO_NEGO_10FULL | AUTO_NEGO_10HALF; rtlsp->stats.speed = RTLS_SPEED_UNKNOWN; rtlsp->stats.duplex = LINK_DUPLEX_UNKNOWN; break; case FORCE_AUTO_NEGO: control |= BASIC_MODE_AUTONEGO | BASIC_MODE_RESTAR_AUTONEGO; ad_mode |= AUTO_NEGO_100FULL | AUTO_NEGO_100HALF | AUTO_NEGO_10FULL | AUTO_NEGO_10HALF; if (!rtlsp->chip_error) { rtlsp->stats.speed = RTLS_SPEED_UNKNOWN; rtlsp->stats.duplex = LINK_DUPLEX_UNKNOWN; } break; case FORCE_100_FDX: /* * RTL8139 can't establish link correctly on this force mode * if the other side is not the same force mode. So We have to * realize this force mode in auto-negotiation mode advertising * 100M/Full ability only */ control |= BASIC_MODE_AUTONEGO | BASIC_MODE_RESTAR_AUTONEGO; ad_mode |= AUTO_NEGO_100FULL; rtlsp->stats.speed = RTLS_SPEED_100M; rtlsp->stats.duplex = LINK_DUPLEX_FULL; break; case FORCE_100_HDX: control |= BASIC_MODE_SPEED_100; ad_mode |= AUTO_NEGO_100HALF; rtlsp->stats.speed = RTLS_SPEED_100M; rtlsp->stats.duplex = LINK_DUPLEX_HALF; break; case FORCE_10_FDX: /* * RTL8139 can't establish link correctly on this force mode * if the other side is not the same force mode. So We have to * realize this force mode in auto-negotiation mode advertising * 10M/Full ability only */ control |= BASIC_MODE_AUTONEGO | BASIC_MODE_RESTAR_AUTONEGO; ad_mode |= AUTO_NEGO_10FULL; rtlsp->stats.speed = RTLS_SPEED_10M; rtlsp->stats.duplex = LINK_DUPLEX_FULL; break; case FORCE_10_HDX: ad_mode |= AUTO_NEGO_10HALF; rtlsp->stats.speed = RTLS_SPEED_10M; rtlsp->stats.duplex = LINK_DUPLEX_HALF; break; } /* * Set auto-negotiation advertisement ability register */ rtls_reg_set16(rtlsp, AUTO_NEGO_AD_REG, ad_mode); /* * Change to config register write enable mode */ val8 = rtls_reg_get8(rtlsp, RT_93c46_COMMOND_REG); val8 |= RT_93c46_MODE_CONFIG; rtls_reg_set8(rtlsp, RT_93c46_COMMOND_REG, val8); /* * Set MII control register */ rtls_reg_set16(rtlsp, BASIC_MODE_CONTROL_REG, control); /* * Return to normal network/host communication mode */ val8 &= ~RT_93c46_MODE_CONFIG; rtls_reg_set8(rtlsp, RT_93c46_COMMOND_REG, val8); } #ifdef RTLS_DEBUG /* * rtls_reg_print() -- print out reg value(for debug use only) */ static void rtls_reg_print(rtls_t *rtlsp) { uint8_t val8; uint16_t val16; uint32_t val32; val8 = rtls_reg_get8(rtlsp, RT_COMMAND_REG); cmn_err(CE_NOTE, "%s: RT_COMMAND_REG = 0x%x", rtlsp->ifname, val8); delay(drv_usectohz(1000)); val16 = rtls_reg_get16(rtlsp, RT_INT_STATUS_REG); cmn_err(CE_NOTE, "%s: RT_INT_STATUS_REG = 0x%x", rtlsp->ifname, val16); delay(drv_usectohz(1000)); val16 = rtls_reg_get16(rtlsp, RT_INT_MASK_REG); cmn_err(CE_NOTE, "%s: RT_INT_MASK_REG = 0x%x", rtlsp->ifname, val16); delay(drv_usectohz(1000)); val32 = rtls_reg_get32(rtlsp, RX_CONFIG_REG); cmn_err(CE_NOTE, "%s: RX_CONFIG_REG = 0x%x", rtlsp->ifname, val32); delay(drv_usectohz(1000)); val16 = rtls_reg_get16(rtlsp, TX_DESC_STAUS_REG); cmn_err(CE_NOTE, "%s: TX_DESC_STAUS_REG = 0x%x, cur_desc = %d", rtlsp->ifname, val16, rtlsp->tx_current_desc); delay(drv_usectohz(1000)); val32 = rtls_reg_get32(rtlsp, TX_STATUS_DESC0_REG); cmn_err(CE_NOTE, "%s: TX_STATUS_DESC0_REG = 0x%x", rtlsp->ifname, val32); delay(drv_usectohz(1000)); val32 = rtls_reg_get32(rtlsp, TX_STATUS_DESC1_REG); cmn_err(CE_NOTE, "%s: TX_STATUS_DESC1_REG = 0x%x", rtlsp->ifname, val32); delay(drv_usectohz(1000)); val32 = rtls_reg_get32(rtlsp, TX_STATUS_DESC2_REG); cmn_err(CE_NOTE, "%s: TX_STATUS_DESC2_REG = 0x%x", rtlsp->ifname, val32); delay(drv_usectohz(1000)); val32 = rtls_reg_get32(rtlsp, TX_STATUS_DESC3_REG); cmn_err(CE_NOTE, "%s: TX_STATUS_DESC3_REG = 0x%x", rtlsp->ifname, val32); delay(drv_usectohz(1000)); cmn_err(CE_NOTE, "%s: in = %llu, multicast = %llu, broadcast = %llu", rtlsp->ifname, (unsigned long long)rtlsp->stats.ipackets, (unsigned long long)rtlsp->stats.multi_rcv, (unsigned long long)rtlsp->stats.brdcst_rcv); delay(drv_usectohz(1000)); } #endif