/* * sfe.c : DP83815/DP83816/SiS900 Fast Ethernet MAC driver for Solaris * * Copyright (c) 2002-2008 Masayuki Murayama. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the author nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * System Header files. */ #include #include #include #include #include #include #include #include #include #include #include #include "sfe_mii.h" #include "sfe_util.h" #include "sfereg.h" char ident[] = "sis900/dp83815 driver v" "2.6.1t30os"; /* Debugging support */ #ifdef DEBUG_LEVEL static int sfe_debug = DEBUG_LEVEL; #if DEBUG_LEVEL > 4 #define CONS "^" #else #define CONS "!" #endif #define DPRINTF(n, args) if (sfe_debug > (n)) cmn_err args #else #define CONS "!" #define DPRINTF(n, args) #endif /* * Useful macros and typedefs */ #define ONESEC (drv_usectohz(1*1000000)) #define ROUNDUP2(x, a) (((x) + (a) - 1) & ~((a) - 1)) /* * Our configuration */ #define MAXTXFRAGS 1 #define MAXRXFRAGS 1 #ifndef TX_BUF_SIZE #define TX_BUF_SIZE 64 #endif #ifndef TX_RING_SIZE #if MAXTXFRAGS == 1 #define TX_RING_SIZE TX_BUF_SIZE #else #define TX_RING_SIZE (TX_BUF_SIZE * 4) #endif #endif #ifndef RX_BUF_SIZE #define RX_BUF_SIZE 256 #endif #ifndef RX_RING_SIZE #define RX_RING_SIZE RX_BUF_SIZE #endif #define OUR_INTR_BITS \ (ISR_DPERR | ISR_SSERR | ISR_RMABT | ISR_RTABT | ISR_RXSOVR | \ ISR_TXURN | ISR_TXDESC | ISR_TXERR | \ ISR_RXORN | ISR_RXIDLE | ISR_RXOK | ISR_RXERR) #define USE_MULTICAST_HASHTBL static int sfe_tx_copy_thresh = 256; static int sfe_rx_copy_thresh = 256; /* special PHY registers for SIS900 */ #define MII_CONFIG1 0x0010 #define MII_CONFIG2 0x0011 #define MII_MASK 0x0013 #define MII_RESV 0x0014 #define PHY_MASK 0xfffffff0 #define PHY_SIS900_INTERNAL 0x001d8000 #define PHY_ICS1893 0x0015f440 #define SFE_DESC_SIZE 16 /* including pads rounding up to power of 2 */ /* * Supported chips */ struct chip_info { uint16_t venid; uint16_t devid; char *chip_name; int chip_type; #define CHIPTYPE_DP83815 0 #define CHIPTYPE_SIS900 1 }; /* * Chip dependent MAC state */ struct sfe_dev { /* misc HW information */ struct chip_info *chip; uint32_t our_intr_bits; uint32_t isr_pended; uint32_t cr; uint_t tx_drain_threshold; uint_t tx_fill_threshold; uint_t rx_drain_threshold; uint_t rx_fill_threshold; uint8_t revid; /* revision from PCI configuration */ boolean_t (*get_mac_addr)(struct gem_dev *); uint8_t mac_addr[ETHERADDRL]; uint8_t bridge_revid; }; /* * Hardware information */ struct chip_info sfe_chiptbl[] = { { 0x1039, 0x0900, "SiS900", CHIPTYPE_SIS900, }, { 0x100b, 0x0020, "DP83815/83816", CHIPTYPE_DP83815, }, { 0x1039, 0x7016, "SiS7016", CHIPTYPE_SIS900, }, }; #define CHIPTABLESIZE (sizeof (sfe_chiptbl)/sizeof (struct chip_info)) /* ======================================================== */ /* mii operations */ static void sfe_mii_sync_dp83815(struct gem_dev *); static void sfe_mii_sync_sis900(struct gem_dev *); static uint16_t sfe_mii_read_dp83815(struct gem_dev *, uint_t); static uint16_t sfe_mii_read_sis900(struct gem_dev *, uint_t); static void sfe_mii_write_dp83815(struct gem_dev *, uint_t, uint16_t); static void sfe_mii_write_sis900(struct gem_dev *, uint_t, uint16_t); static void sfe_set_eq_sis630(struct gem_dev *); /* nic operations */ static int sfe_reset_chip_sis900(struct gem_dev *); static int sfe_reset_chip_dp83815(struct gem_dev *); static int sfe_init_chip(struct gem_dev *); static int sfe_start_chip(struct gem_dev *); static int sfe_stop_chip(struct gem_dev *); static int sfe_set_media(struct gem_dev *); static int sfe_set_rx_filter_dp83815(struct gem_dev *); static int sfe_set_rx_filter_sis900(struct gem_dev *); static int sfe_get_stats(struct gem_dev *); static int sfe_attach_chip(struct gem_dev *); /* descriptor operations */ static int sfe_tx_desc_write(struct gem_dev *dp, int slot, ddi_dma_cookie_t *dmacookie, int frags, uint64_t flags); static void sfe_tx_start(struct gem_dev *dp, int startslot, int nslot); static void sfe_rx_desc_write(struct gem_dev *dp, int slot, ddi_dma_cookie_t *dmacookie, int frags); static uint_t sfe_tx_desc_stat(struct gem_dev *dp, int slot, int ndesc); static uint64_t sfe_rx_desc_stat(struct gem_dev *dp, int slot, int ndesc); static void sfe_tx_desc_init(struct gem_dev *dp, int slot); static void sfe_rx_desc_init(struct gem_dev *dp, int slot); static void sfe_tx_desc_clean(struct gem_dev *dp, int slot); static void sfe_rx_desc_clean(struct gem_dev *dp, int slot); /* interrupt handler */ static uint_t sfe_interrupt(struct gem_dev *dp); /* ======================================================== */ /* mapping attributes */ /* Data access requirements. */ static struct ddi_device_acc_attr sfe_dev_attr = { DDI_DEVICE_ATTR_V0, DDI_STRUCTURE_LE_ACC, DDI_STRICTORDER_ACC }; /* On sparc, Buffers should be native endian for speed */ static struct ddi_device_acc_attr sfe_buf_attr = { DDI_DEVICE_ATTR_V0, DDI_NEVERSWAP_ACC, /* native endianness */ DDI_STRICTORDER_ACC }; static ddi_dma_attr_t sfe_dma_attr_buf = { DMA_ATTR_V0, /* dma_attr_version */ 0, /* dma_attr_addr_lo */ 0xffffffffull, /* dma_attr_addr_hi */ 0x00000fffull, /* dma_attr_count_max */ 0, /* patched later */ /* dma_attr_align */ 0x000003fc, /* dma_attr_burstsizes */ 1, /* dma_attr_minxfer */ 0x00000fffull, /* dma_attr_maxxfer */ 0xffffffffull, /* dma_attr_seg */ 0, /* patched later */ /* dma_attr_sgllen */ 1, /* dma_attr_granular */ 0 /* dma_attr_flags */ }; static ddi_dma_attr_t sfe_dma_attr_desc = { DMA_ATTR_V0, /* dma_attr_version */ 16, /* dma_attr_addr_lo */ 0xffffffffull, /* dma_attr_addr_hi */ 0xffffffffull, /* dma_attr_count_max */ 16, /* dma_attr_align */ 0x000003fc, /* dma_attr_burstsizes */ 1, /* dma_attr_minxfer */ 0xffffffffull, /* dma_attr_maxxfer */ 0xffffffffull, /* dma_attr_seg */ 1, /* dma_attr_sgllen */ 1, /* dma_attr_granular */ 0 /* dma_attr_flags */ }; uint32_t sfe_use_pcimemspace = 0; /* ======================================================== */ /* * HW manipulation routines */ /* ======================================================== */ #define SFE_EEPROM_DELAY(dp) \ { (void) INL(dp, EROMAR); (void) INL(dp, EROMAR); } #define EE_CMD_READ 6 #define EE_CMD_SHIFT 6 static uint16_t sfe_read_eeprom(struct gem_dev *dp, uint_t offset) { int eedi; int i; uint16_t ret; /* ensure de-assert chip select */ OUTL(dp, EROMAR, 0); SFE_EEPROM_DELAY(dp); OUTL(dp, EROMAR, EROMAR_EESK); SFE_EEPROM_DELAY(dp); /* assert chip select */ offset |= EE_CMD_READ << EE_CMD_SHIFT; for (i = 8; i >= 0; i--) { /* make command */ eedi = ((offset >> i) & 1) << EROMAR_EEDI_SHIFT; /* send 1 bit */ OUTL(dp, EROMAR, EROMAR_EECS | eedi); SFE_EEPROM_DELAY(dp); OUTL(dp, EROMAR, EROMAR_EECS | eedi | EROMAR_EESK); SFE_EEPROM_DELAY(dp); } OUTL(dp, EROMAR, EROMAR_EECS); ret = 0; for (i = 0; i < 16; i++) { /* Get 1 bit */ OUTL(dp, EROMAR, EROMAR_EECS); SFE_EEPROM_DELAY(dp); OUTL(dp, EROMAR, EROMAR_EECS | EROMAR_EESK); SFE_EEPROM_DELAY(dp); ret = (ret << 1) | ((INL(dp, EROMAR) >> EROMAR_EEDO_SHIFT) & 1); } OUTL(dp, EROMAR, 0); SFE_EEPROM_DELAY(dp); return (ret); } #undef SFE_EEPROM_DELAY static boolean_t sfe_get_mac_addr_dp83815(struct gem_dev *dp) { uint8_t *mac; uint_t val; int i; #define BITSET(p, ix, v) (p)[(ix)/8] |= ((v) ? 1 : 0) << ((ix) & 0x7) DPRINTF(4, (CE_CONT, CONS "%s: %s: called", dp->name, __func__)); mac = dp->dev_addr.ether_addr_octet; /* first of all, clear MAC address buffer */ bzero(mac, ETHERADDRL); /* get bit 0 */ val = sfe_read_eeprom(dp, 0x6); BITSET(mac, 0, val & 1); /* get bit 1 - 16 */ val = sfe_read_eeprom(dp, 0x7); for (i = 0; i < 16; i++) { BITSET(mac, 1 + i, val & (1 << (15 - i))); } /* get bit 17 - 32 */ val = sfe_read_eeprom(dp, 0x8); for (i = 0; i < 16; i++) { BITSET(mac, 17 + i, val & (1 << (15 - i))); } /* get bit 33 - 47 */ val = sfe_read_eeprom(dp, 0x9); for (i = 0; i < 15; i++) { BITSET(mac, 33 + i, val & (1 << (15 - i))); } return (B_TRUE); #undef BITSET } static boolean_t sfe_get_mac_addr_sis900(struct gem_dev *dp) { uint_t val; int i; uint8_t *mac; mac = dp->dev_addr.ether_addr_octet; for (i = 0; i < ETHERADDRL/2; i++) { val = sfe_read_eeprom(dp, 0x8 + i); *mac++ = (uint8_t)val; *mac++ = (uint8_t)(val >> 8); } return (B_TRUE); } static dev_info_t * sfe_search_pci_dev_subr(dev_info_t *cur_node, int vendor_id, int device_id) { dev_info_t *child_id; dev_info_t *ret; int vid, did; if (cur_node == NULL) { return (NULL); } /* check brothers */ do { vid = ddi_prop_get_int(DDI_DEV_T_ANY, cur_node, DDI_PROP_DONTPASS, "vendor-id", -1); did = ddi_prop_get_int(DDI_DEV_T_ANY, cur_node, DDI_PROP_DONTPASS, "device-id", -1); if (vid == vendor_id && did == device_id) { /* found */ return (cur_node); } /* check children */ if ((child_id = ddi_get_child(cur_node)) != NULL) { if ((ret = sfe_search_pci_dev_subr(child_id, vendor_id, device_id)) != NULL) { return (ret); } } } while ((cur_node = ddi_get_next_sibling(cur_node)) != NULL); /* not found */ return (NULL); } static dev_info_t * sfe_search_pci_dev(int vendor_id, int device_id) { return (sfe_search_pci_dev_subr(ddi_root_node(), vendor_id, device_id)); } static boolean_t sfe_get_mac_addr_sis962(struct gem_dev *dp) { boolean_t ret; int i; ret = B_FALSE; /* rise request signal to access EEPROM */ OUTL(dp, MEAR, EROMAR_EEREQ); for (i = 0; (INL(dp, MEAR) & EROMAR_EEGNT) == 0; i++) { if (i > 200) { /* failed to acquire eeprom */ cmn_err(CE_NOTE, CONS "%s: failed to access eeprom", dp->name); goto x; } drv_usecwait(10); } ret = sfe_get_mac_addr_sis900(dp); x: /* release EEPROM */ OUTL(dp, MEAR, EROMAR_EEDONE); return (ret); } static int sfe_reset_chip_sis900(struct gem_dev *dp) { int i; uint32_t done; uint32_t val; struct sfe_dev *lp = dp->private; DPRINTF(4, (CE_CONT, CONS "%s: %s called", dp->name, __func__)); /* invalidate mac addr cache */ bzero(lp->mac_addr, sizeof (lp->mac_addr)); lp->cr = 0; /* inhibit interrupt */ OUTL(dp, IMR, 0); lp->isr_pended |= INL(dp, ISR) & lp->our_intr_bits; OUTLINL(dp, RFCR, 0); OUTL(dp, CR, CR_RST | CR_TXR | CR_RXR); drv_usecwait(10); done = 0; for (i = 0; done != (ISR_TXRCMP | ISR_RXRCMP); i++) { if (i > 1000) { cmn_err(CE_WARN, "%s: chip reset timeout", dp->name); return (GEM_FAILURE); } done |= INL(dp, ISR) & (ISR_TXRCMP | ISR_RXRCMP); drv_usecwait(10); } if (lp->revid == SIS630ET_900_REV) { lp->cr |= CR_ACCESSMODE; OUTL(dp, CR, lp->cr | INL(dp, CR)); } /* Configuration register: enable PCI parity */ DPRINTF(2, (CE_CONT, CONS "%s: cfg:%b", dp->name, INL(dp, CFG), CFG_BITS_SIS900)); val = 0; if (lp->revid >= SIS635A_900_REV || lp->revid == SIS900B_900_REV) { /* what is this ? */ val |= CFG_RND_CNT; } OUTL(dp, CFG, val); DPRINTF(2, (CE_CONT, CONS "%s: cfg:%b", dp->name, INL(dp, CFG), CFG_BITS_SIS900)); return (GEM_SUCCESS); } static int sfe_reset_chip_dp83815(struct gem_dev *dp) { int i; uint32_t val; struct sfe_dev *lp = dp->private; DPRINTF(4, (CE_CONT, CONS "%s: %s called", dp->name, __func__)); /* invalidate mac addr cache */ bzero(lp->mac_addr, sizeof (lp->mac_addr)); lp->cr = 0; /* inhibit interrupts */ OUTL(dp, IMR, 0); lp->isr_pended |= INL(dp, ISR) & lp->our_intr_bits; OUTL(dp, RFCR, 0); OUTL(dp, CR, CR_RST); drv_usecwait(10); for (i = 0; INL(dp, CR) & CR_RST; i++) { if (i > 100) { cmn_err(CE_WARN, "!%s: chip reset timeout", dp->name); return (GEM_FAILURE); } drv_usecwait(10); } DPRINTF(0, (CE_CONT, "!%s: chip reset in %duS", dp->name, i*10)); OUTL(dp, CCSR, CCSR_PMESTS); OUTL(dp, CCSR, 0); /* Configuration register: enable PCI parity */ DPRINTF(2, (CE_CONT, CONS "%s: cfg:%b", dp->name, INL(dp, CFG), CFG_BITS_DP83815)); val = INL(dp, CFG) & (CFG_ANEG_SEL | CFG_PHY_CFG); OUTL(dp, CFG, val | CFG_PAUSE_ADV); DPRINTF(2, (CE_CONT, CONS "%s: cfg:%b", dp->name, INL(dp, CFG), CFG_BITS_DP83815)); return (GEM_SUCCESS); } static int sfe_init_chip(struct gem_dev *dp) { /* Configuration register: have been set up in sfe_chip_reset */ /* PCI test control register: do nothing */ /* Interrupt status register : do nothing */ /* Interrupt mask register: clear, but leave lp->our_intr_bits */ OUTL(dp, IMR, 0); /* Enhanced PHY Access register (sis900): do nothing */ /* Transmit Descriptor Pointer register: base addr of TX ring */ OUTL(dp, TXDP, dp->tx_ring_dma); /* Receive descriptor pointer register: base addr of RX ring */ OUTL(dp, RXDP, dp->rx_ring_dma); return (GEM_SUCCESS); } static uint_t sfe_mcast_hash(struct gem_dev *dp, uint8_t *addr) { return (gem_ether_crc_be(addr, ETHERADDRL)); } #ifdef DEBUG_LEVEL static void sfe_rxfilter_dump(struct gem_dev *dp, int start, int end) { int i; int j; uint16_t ram[0x10]; cmn_err(CE_CONT, "!%s: rx filter ram dump:", dp->name); #define WORDS_PER_LINE 4 for (i = start; i < end; i += WORDS_PER_LINE*2) { for (j = 0; j < WORDS_PER_LINE; j++) { OUTL(dp, RFCR, RFADDR_MAC_DP83815 + i + j*2); ram[j] = INL(dp, RFDR); } cmn_err(CE_CONT, "!0x%02x: 0x%04x 0x%04x 0x%04x 0x%04x", i, ram[0], ram[1], ram[2], ram[3]); } #undef WORDS_PER_LINE } #endif static uint_t sfe_rf_perfect_base_dp83815[] = { RFADDR_PMATCH0_DP83815, RFADDR_PMATCH1_DP83815, RFADDR_PMATCH2_DP83815, RFADDR_PMATCH3_DP83815, }; static int sfe_set_rx_filter_dp83815(struct gem_dev *dp) { int i; int j; uint32_t mode; uint8_t *mac = dp->cur_addr.ether_addr_octet; uint16_t hash_tbl[32]; struct sfe_dev *lp = dp->private; DPRINTF(1, (CE_CONT, CONS "%s: %s: called, mc_count:%d, mode:0x%b", dp->name, __func__, dp->mc_count, dp->rxmode, RXMODE_BITS)); #if DEBUG_LEVEL > 0 for (i = 0; i < dp->mc_count; i++) { cmn_err(CE_CONT, "!%s: adding mcast(%d) %02x:%02x:%02x:%02x:%02x:%02x", dp->name, i, dp->mc_list[i].addr.ether_addr_octet[0], dp->mc_list[i].addr.ether_addr_octet[1], dp->mc_list[i].addr.ether_addr_octet[2], dp->mc_list[i].addr.ether_addr_octet[3], dp->mc_list[i].addr.ether_addr_octet[4], dp->mc_list[i].addr.ether_addr_octet[5]); } #endif if ((dp->rxmode & RXMODE_ENABLE) == 0) { /* disable rx filter */ OUTL(dp, RFCR, 0); return (GEM_SUCCESS); } /* * Set Receive filter control register */ if (dp->rxmode & RXMODE_PROMISC) { /* all broadcast, all multicast, all physical */ mode = RFCR_AAB | RFCR_AAM | RFCR_AAP; } else if ((dp->rxmode & RXMODE_ALLMULTI) || dp->mc_count > 16*32/2) { /* all broadcast, all multicast, physical for the chip */ mode = RFCR_AAB | RFCR_AAM | RFCR_APM_DP83815; } else if (dp->mc_count > 4) { /* * Use multicast hash table, * accept all broadcast and physical for the chip. */ mode = RFCR_AAB | RFCR_MHEN_DP83815 | RFCR_APM_DP83815; bzero(hash_tbl, sizeof (hash_tbl)); for (i = 0; i < dp->mc_count; i++) { j = dp->mc_list[i].hash >> (32 - 9); hash_tbl[j / 16] |= 1 << (j % 16); } } else { /* * Use pattern mach filter for multicast address, * accept all broadcast and physical for the chip */ /* need to enable corresponding pattern registers */ mode = RFCR_AAB | RFCR_APM_DP83815 | (((1 << dp->mc_count) - 1) << RFCR_APAT_SHIFT); } #if DEBUG_LEVEL > 1 cmn_err(CE_CONT, "!%s: mac %02x:%02x:%02x:%02x:%02x:%02x" " cache %02x:%02x:%02x:%02x:%02x:%02x", dp->name, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], lp->mac_addr[0], lp->mac_addr[1], lp->mac_addr[2], lp->mac_addr[3], lp->mac_addr[4], lp->mac_addr[5]); #endif if (bcmp(mac, lp->mac_addr, ETHERADDRL) != 0) { /* * XXX - need to *disable* rx filter to load mac address for * the chip. otherwise, we cannot setup rxfilter correctly. */ /* setup perfect match register for my station address */ for (i = 0; i < ETHERADDRL; i += 2) { OUTL(dp, RFCR, RFADDR_MAC_DP83815 + i); OUTL(dp, RFDR, (mac[i+1] << 8) | mac[i]); } bcopy(mac, lp->mac_addr, ETHERADDRL); } #if DEBUG_LEVEL > 3 /* clear pattern ram */ for (j = 0x200; j < 0x380; j += 2) { OUTL(dp, RFCR, j); OUTL(dp, RFDR, 0); } #endif if (mode & RFCR_APAT_DP83815) { /* setup multicast address into pattern match registers */ for (j = 0; j < dp->mc_count; j++) { mac = &dp->mc_list[j].addr.ether_addr_octet[0]; for (i = 0; i < ETHERADDRL; i += 2) { OUTL(dp, RFCR, sfe_rf_perfect_base_dp83815[j] + i*2); OUTL(dp, RFDR, (mac[i+1] << 8) | mac[i]); } } /* setup pattern count registers */ OUTL(dp, RFCR, RFADDR_PCOUNT01_DP83815); OUTL(dp, RFDR, (ETHERADDRL << 8) | ETHERADDRL); OUTL(dp, RFCR, RFADDR_PCOUNT23_DP83815); OUTL(dp, RFDR, (ETHERADDRL << 8) | ETHERADDRL); } if (mode & RFCR_MHEN_DP83815) { /* Load Multicast hash table */ for (i = 0; i < 32; i++) { /* for DP83815, index is in byte */ OUTL(dp, RFCR, RFADDR_MULTICAST_DP83815 + i*2); OUTL(dp, RFDR, hash_tbl[i]); } } #if DEBUG_LEVEL > 2 sfe_rxfilter_dump(dp, 0, 0x10); sfe_rxfilter_dump(dp, 0x200, 0x380); #endif /* Set rx filter mode and enable rx filter */ OUTL(dp, RFCR, RFCR_RFEN | mode); return (GEM_SUCCESS); } static int sfe_set_rx_filter_sis900(struct gem_dev *dp) { int i; uint32_t mode; uint16_t hash_tbl[16]; uint8_t *mac = dp->cur_addr.ether_addr_octet; int hash_size; int hash_shift; struct sfe_dev *lp = dp->private; DPRINTF(4, (CE_CONT, CONS "%s: %s: called", dp->name, __func__)); if ((dp->rxmode & RXMODE_ENABLE) == 0) { /* disable rx filter */ OUTLINL(dp, RFCR, 0); return (GEM_SUCCESS); } /* * determine hardware hash table size in word. */ hash_shift = 25; if (lp->revid >= SIS635A_900_REV || lp->revid == SIS900B_900_REV) { hash_shift = 24; } hash_size = (1 << (32 - hash_shift)) / 16; bzero(hash_tbl, sizeof (hash_tbl)); /* Set Receive filter control register */ if (dp->rxmode & RXMODE_PROMISC) { /* all broadcast, all multicast, all physical */ mode = RFCR_AAB | RFCR_AAM | RFCR_AAP; } else if ((dp->rxmode & RXMODE_ALLMULTI) || dp->mc_count > hash_size*16/2) { /* all broadcast, all multicast, physical for the chip */ mode = RFCR_AAB | RFCR_AAM; } else { /* all broadcast, physical for the chip */ mode = RFCR_AAB; } /* make hash table */ for (i = 0; i < dp->mc_count; i++) { uint_t h; h = dp->mc_list[i].hash >> hash_shift; hash_tbl[h / 16] |= 1 << (h % 16); } if (bcmp(mac, lp->mac_addr, ETHERADDRL) != 0) { /* Disable Rx filter and load mac address */ for (i = 0; i < ETHERADDRL/2; i++) { /* For sis900, index is in word */ OUTLINL(dp, RFCR, (RFADDR_MAC_SIS900+i) << RFCR_RFADDR_SHIFT_SIS900); OUTLINL(dp, RFDR, (mac[i*2+1] << 8) | mac[i*2]); } bcopy(mac, lp->mac_addr, ETHERADDRL); } /* Load Multicast hash table */ for (i = 0; i < hash_size; i++) { /* For sis900, index is in word */ OUTLINL(dp, RFCR, (RFADDR_MULTICAST_SIS900 + i) << RFCR_RFADDR_SHIFT_SIS900); OUTLINL(dp, RFDR, hash_tbl[i]); } /* Load rx filter mode and enable rx filter */ OUTLINL(dp, RFCR, RFCR_RFEN | mode); return (GEM_SUCCESS); } static int sfe_start_chip(struct gem_dev *dp) { struct sfe_dev *lp = dp->private; DPRINTF(4, (CE_CONT, CONS "%s: %s: called", dp->name, __func__)); /* * setup interrupt mask, which shouldn't include ISR_TOK * to improve performance. */ lp->our_intr_bits = OUR_INTR_BITS; /* enable interrupt */ if ((dp->misc_flag & GEM_NOINTR) == 0) { OUTL(dp, IER, 1); OUTL(dp, IMR, lp->our_intr_bits); } /* Kick RX */ OUTL(dp, CR, lp->cr | CR_RXE); return (GEM_SUCCESS); } /* * Stop nic core gracefully. */ static int sfe_stop_chip(struct gem_dev *dp) { struct sfe_dev *lp = dp->private; uint32_t done; int i; uint32_t val; DPRINTF(4, (CE_CONT, CONS "%s: %s: called", dp->name, __func__)); /* * Although we inhibit interrupt here, we don't clear soft copy of * interrupt mask to avoid bogus interrupts. */ OUTL(dp, IMR, 0); /* stop TX and RX immediately */ OUTL(dp, CR, lp->cr | CR_TXR | CR_RXR); done = 0; for (i = 0; done != (ISR_RXRCMP | ISR_TXRCMP); i++) { if (i > 1000) { /* * As gem layer will call sfe_reset_chip(), * we don't neet to reset futher */ cmn_err(CE_NOTE, "!%s: %s: Tx/Rx reset timeout", dp->name, __func__); return (GEM_FAILURE); } val = INL(dp, ISR); done |= val & (ISR_RXRCMP | ISR_TXRCMP); lp->isr_pended |= val & lp->our_intr_bits; drv_usecwait(10); } return (GEM_SUCCESS); } #ifndef __sparc /* * Stop nic core gracefully for quiesce */ static int sfe_stop_chip_quiesce(struct gem_dev *dp) { struct sfe_dev *lp = dp->private; uint32_t done; int i; uint32_t val; /* * Although we inhibit interrupt here, we don't clear soft copy of * interrupt mask to avoid bogus interrupts. */ OUTL(dp, IMR, 0); /* stop TX and RX immediately */ OUTL(dp, CR, CR_TXR | CR_RXR); done = 0; for (i = 0; done != (ISR_RXRCMP | ISR_TXRCMP); i++) { if (i > 1000) { /* * As gem layer will call sfe_reset_chip(), * we don't neet to reset futher */ return (DDI_FAILURE); } val = INL(dp, ISR); done |= val & (ISR_RXRCMP | ISR_TXRCMP); lp->isr_pended |= val & lp->our_intr_bits; drv_usecwait(10); } return (DDI_SUCCESS); } #endif /* * Setup media mode */ static uint_t sfe_mxdma_value[] = { 512, 4, 8, 16, 32, 64, 128, 256, }; static uint_t sfe_encode_mxdma(uint_t burstsize) { int i; if (burstsize > 256) { /* choose 512 */ return (0); } for (i = 1; i < 8; i++) { if (burstsize <= sfe_mxdma_value[i]) { break; } } return (i); } static int sfe_set_media(struct gem_dev *dp) { uint32_t txcfg; uint32_t rxcfg; uint32_t pcr; uint32_t val; uint32_t txmxdma; uint32_t rxmxdma; struct sfe_dev *lp = dp->private; #ifdef DEBUG_LEVEL extern int gem_speed_value[]; #endif DPRINTF(2, (CE_CONT, CONS "%s: %s: %s duplex, %d Mbps", dp->name, __func__, dp->full_duplex ? "full" : "half", gem_speed_value[dp->speed])); /* initialize txcfg and rxcfg */ txcfg = TXCFG_ATP; if (dp->full_duplex) { txcfg |= (TXCFG_CSI | TXCFG_HBI); } rxcfg = RXCFG_AEP | RXCFG_ARP; if (dp->full_duplex) { rxcfg |= RXCFG_ATX; } /* select txmxdma and rxmxdma, maxmum burst length */ if (lp->chip->chip_type == CHIPTYPE_SIS900) { #ifdef DEBUG_SIS900_EDB val = CFG_EDB_MASTER; #else val = INL(dp, CFG) & CFG_EDB_MASTER; #endif if (val) { /* * sis900 built-in cores: * max burst length must be fixed to 64 */ txmxdma = 64; rxmxdma = 64; } else { /* * sis900 pci chipset: * the vendor recommended to fix max burst length * to 512 */ txmxdma = 512; rxmxdma = 512; } } else { /* * NS dp83815/816: * use user defined or default for tx/rx max burst length */ txmxdma = max(dp->txmaxdma, 256); rxmxdma = max(dp->rxmaxdma, 256); } /* tx high water mark */ lp->tx_drain_threshold = ROUNDUP2(dp->txthr, TXCFG_FIFO_UNIT); /* determine tx_fill_threshold accroding drain threshold */ lp->tx_fill_threshold = TXFIFOSIZE - lp->tx_drain_threshold - TXCFG_FIFO_UNIT; /* tune txmxdma not to exceed tx_fill_threshold */ for (; ; ) { /* normalize txmxdma requested */ val = sfe_encode_mxdma(txmxdma); txmxdma = sfe_mxdma_value[val]; if (txmxdma <= lp->tx_fill_threshold) { break; } /* select new txmxdma */ txmxdma = txmxdma / 2; } txcfg |= val << TXCFG_MXDMA_SHIFT; /* encode rxmxdma, maxmum burst length for rx */ val = sfe_encode_mxdma(rxmxdma); rxcfg |= val << RXCFG_MXDMA_SHIFT; rxmxdma = sfe_mxdma_value[val]; /* receive starting threshold - it have only 5bit-wide field */ val = ROUNDUP2(max(dp->rxthr, ETHERMIN), RXCFG_FIFO_UNIT); lp->rx_drain_threshold = min(val, (RXCFG_DRTH >> RXCFG_DRTH_SHIFT) * RXCFG_FIFO_UNIT); DPRINTF(0, (CE_CONT, "%s: %s: tx: drain:%d(rest %d) fill:%d mxdma:%d," " rx: drain:%d mxdma:%d", dp->name, __func__, lp->tx_drain_threshold, TXFIFOSIZE - lp->tx_drain_threshold, lp->tx_fill_threshold, txmxdma, lp->rx_drain_threshold, rxmxdma)); ASSERT(lp->tx_drain_threshold < 64*TXCFG_FIFO_UNIT); ASSERT(lp->tx_fill_threshold < 64*TXCFG_FIFO_UNIT); ASSERT(lp->rx_drain_threshold < 32*RXCFG_FIFO_UNIT); txcfg |= ((lp->tx_fill_threshold/TXCFG_FIFO_UNIT) << TXCFG_FLTH_SHIFT) | (lp->tx_drain_threshold/TXCFG_FIFO_UNIT); OUTL(dp, TXCFG, txcfg); rxcfg |= ((lp->rx_drain_threshold/RXCFG_FIFO_UNIT) << RXCFG_DRTH_SHIFT); if (lp->chip->chip_type == CHIPTYPE_DP83815) { rxcfg |= RXCFG_ALP_DP83815; } OUTL(dp, RXCFG, rxcfg); DPRINTF(0, (CE_CONT, CONS "%s: %s: txcfg:%b rxcfg:%b", dp->name, __func__, txcfg, TXCFG_BITS, rxcfg, RXCFG_BITS)); /* Flow control */ if (lp->chip->chip_type == CHIPTYPE_DP83815) { pcr = INL(dp, PCR); switch (dp->flow_control) { case FLOW_CONTROL_SYMMETRIC: case FLOW_CONTROL_RX_PAUSE: OUTL(dp, PCR, pcr | PCR_PSEN | PCR_PS_MCAST); break; default: OUTL(dp, PCR, pcr & ~(PCR_PSEN | PCR_PS_MCAST | PCR_PS_DA)); break; } DPRINTF(2, (CE_CONT, CONS "%s: PCR: %b", dp->name, INL(dp, PCR), PCR_BITS)); } else if (lp->chip->chip_type == CHIPTYPE_SIS900) { switch (dp->flow_control) { case FLOW_CONTROL_SYMMETRIC: case FLOW_CONTROL_RX_PAUSE: OUTL(dp, FLOWCTL, FLOWCTL_FLOWEN); break; default: OUTL(dp, FLOWCTL, 0); break; } DPRINTF(2, (CE_CONT, CONS "%s: FLOWCTL: %b", dp->name, INL(dp, FLOWCTL), FLOWCTL_BITS)); } return (GEM_SUCCESS); } static int sfe_get_stats(struct gem_dev *dp) { /* do nothing */ return (GEM_SUCCESS); } /* * descriptor manipulations */ static int sfe_tx_desc_write(struct gem_dev *dp, int slot, ddi_dma_cookie_t *dmacookie, int frags, uint64_t flags) { uint32_t mark; struct sfe_desc *tdp; ddi_dma_cookie_t *dcp; uint32_t tmp0; #if DEBUG_LEVEL > 2 int i; cmn_err(CE_CONT, CONS "%s: time:%d %s seqnum: %d, slot %d, frags: %d flags: %llx", dp->name, ddi_get_lbolt(), __func__, dp->tx_desc_tail, slot, frags, flags); for (i = 0; i < frags; i++) { cmn_err(CE_CONT, CONS "%d: addr: 0x%x, len: 0x%x", i, dmacookie[i].dmac_address, dmacookie[i].dmac_size); } #endif /* * write tx descriptor in reversed order. */ #if DEBUG_LEVEL > 3 flags |= GEM_TXFLAG_INTR; #endif mark = (flags & GEM_TXFLAG_INTR) ? (CMDSTS_OWN | CMDSTS_INTR) : CMDSTS_OWN; ASSERT(frags == 1); dcp = &dmacookie[0]; if (flags & GEM_TXFLAG_HEAD) { mark &= ~CMDSTS_OWN; } tdp = (void *)&dp->tx_ring[SFE_DESC_SIZE * slot]; tmp0 = (uint32_t)dcp->dmac_address; mark |= (uint32_t)dcp->dmac_size; tdp->d_bufptr = LE_32(tmp0); tdp->d_cmdsts = LE_32(mark); return (frags); } static void sfe_tx_start(struct gem_dev *dp, int start_slot, int nslot) { uint_t tx_ring_size = dp->gc.gc_tx_ring_size; struct sfe_desc *tdp; struct sfe_dev *lp = dp->private; if (nslot > 1) { gem_tx_desc_dma_sync(dp, SLOT(start_slot + 1, tx_ring_size), nslot - 1, DDI_DMA_SYNC_FORDEV); } tdp = (void *)&dp->tx_ring[SFE_DESC_SIZE * start_slot]; tdp->d_cmdsts |= LE_32(CMDSTS_OWN); gem_tx_desc_dma_sync(dp, start_slot, 1, DDI_DMA_SYNC_FORDEV); /* * Let the Transmit Buffer Manager Fill state machine active. */ if (dp->mac_active) { OUTL(dp, CR, lp->cr | CR_TXE); } } static void sfe_rx_desc_write(struct gem_dev *dp, int slot, ddi_dma_cookie_t *dmacookie, int frags) { struct sfe_desc *rdp; uint32_t tmp0; uint32_t tmp1; #if DEBUG_LEVEL > 2 int i; ASSERT(frags == 1); cmn_err(CE_CONT, CONS "%s: %s seqnum: %d, slot %d, frags: %d", dp->name, __func__, dp->rx_active_tail, slot, frags); for (i = 0; i < frags; i++) { cmn_err(CE_CONT, CONS " frag: %d addr: 0x%llx, len: 0x%lx", i, dmacookie[i].dmac_address, dmacookie[i].dmac_size); } #endif /* for the last slot of the packet */ rdp = (void *)&dp->rx_ring[SFE_DESC_SIZE * slot]; tmp0 = (uint32_t)dmacookie->dmac_address; tmp1 = CMDSTS_INTR | (uint32_t)dmacookie->dmac_size; rdp->d_bufptr = LE_32(tmp0); rdp->d_cmdsts = LE_32(tmp1); } static uint_t sfe_tx_desc_stat(struct gem_dev *dp, int slot, int ndesc) { uint_t tx_ring_size = dp->gc.gc_tx_ring_size; struct sfe_desc *tdp; uint32_t status; int cols; struct sfe_dev *lp = dp->private; #ifdef DEBUG_LEVEL int i; clock_t delay; #endif /* check status of the last descriptor */ tdp = (void *) &dp->tx_ring[SFE_DESC_SIZE * SLOT(slot + ndesc - 1, tx_ring_size)]; /* * Don't use LE_32() directly to refer tdp->d_cmdsts. * It is not atomic for big endian cpus. */ status = tdp->d_cmdsts; status = LE_32(status); DPRINTF(2, (CE_CONT, CONS "%s: time:%ld %s: slot:%d, status:0x%b", dp->name, ddi_get_lbolt(), __func__, slot, status, TXSTAT_BITS)); if (status & CMDSTS_OWN) { /* * not yet transmitted */ /* workaround for tx hang */ if (lp->chip->chip_type == CHIPTYPE_DP83815 && dp->mac_active) { OUTL(dp, CR, lp->cr | CR_TXE); } return (0); } if (status & CMDSTS_MORE) { /* XXX - the hardware problem but don't panic the system */ /* avoid lint bug for %b format string including 32nd bit */ cmn_err(CE_NOTE, CONS "%s: tx status bits incorrect: slot:%d, status:0x%x", dp->name, slot, status); } #if DEBUG_LEVEL > 3 delay = (ddi_get_lbolt() - dp->tx_buf_head->txb_stime) * 10; if (delay >= 50) { DPRINTF(0, (CE_NOTE, "%s: tx deferred %d mS: slot %d", dp->name, delay, slot)); } #endif #if DEBUG_LEVEL > 3 for (i = 0; i < nfrag-1; i++) { uint32_t s; int n; n = SLOT(slot + i, tx_ring_size); s = LE_32( ((struct sfe_desc *)((void *) &dp->tx_ring[SFE_DESC_SIZE * n]))->d_cmdsts); ASSERT(s & CMDSTS_MORE); ASSERT((s & CMDSTS_OWN) == 0); } #endif /* * collect statistics */ if ((status & CMDSTS_OK) == 0) { /* failed to transmit the packet */ DPRINTF(0, (CE_CONT, CONS "%s: Transmit error, Tx status %b", dp->name, status, TXSTAT_BITS)); dp->stats.errxmt++; if (status & CMDSTS_TFU) { dp->stats.underflow++; } else if (status & CMDSTS_CRS) { dp->stats.nocarrier++; } else if (status & CMDSTS_OWC) { dp->stats.xmtlatecoll++; } else if ((!dp->full_duplex) && (status & CMDSTS_EC)) { dp->stats.excoll++; dp->stats.collisions += 16; } else { dp->stats.xmit_internal_err++; } } else if (!dp->full_duplex) { cols = (status >> CMDSTS_CCNT_SHIFT) & CCNT_MASK; if (cols > 0) { if (cols == 1) { dp->stats.first_coll++; } else /* (cols > 1) */ { dp->stats.multi_coll++; } dp->stats.collisions += cols; } else if (status & CMDSTS_TD) { dp->stats.defer++; } } return (GEM_TX_DONE); } static uint64_t sfe_rx_desc_stat(struct gem_dev *dp, int slot, int ndesc) { struct sfe_desc *rdp; uint_t len; uint_t flag; uint32_t status; flag = GEM_RX_DONE; /* Dont read ISR because we cannot ack only to rx interrupt. */ rdp = (void *)&dp->rx_ring[SFE_DESC_SIZE * slot]; /* * Don't use LE_32() directly to refer rdp->d_cmdsts. * It is not atomic for big endian cpus. */ status = rdp->d_cmdsts; status = LE_32(status); DPRINTF(2, (CE_CONT, CONS "%s: time:%ld %s: slot:%d, status:0x%b", dp->name, ddi_get_lbolt(), __func__, slot, status, RXSTAT_BITS)); if ((status & CMDSTS_OWN) == 0) { /* * No more received packets because * this buffer is owned by NIC. */ return (0); } #define RX_ERR_BITS \ (CMDSTS_RXA | CMDSTS_RXO | CMDSTS_LONG | CMDSTS_RUNT | \ CMDSTS_ISE | CMDSTS_CRCE | CMDSTS_FAE | CMDSTS_MORE) if (status & RX_ERR_BITS) { /* * Packet with error received */ DPRINTF(0, (CE_CONT, CONS "%s: Corrupted packet " "received, buffer status: %b", dp->name, status, RXSTAT_BITS)); /* collect statistics information */ dp->stats.errrcv++; if (status & CMDSTS_RXO) { dp->stats.overflow++; } else if (status & (CMDSTS_LONG | CMDSTS_MORE)) { dp->stats.frame_too_long++; } else if (status & CMDSTS_RUNT) { dp->stats.runt++; } else if (status & (CMDSTS_ISE | CMDSTS_FAE)) { dp->stats.frame++; } else if (status & CMDSTS_CRCE) { dp->stats.crc++; } else { dp->stats.rcv_internal_err++; } return (flag | GEM_RX_ERR); } /* * this packet was received without errors */ if ((len = (status & CMDSTS_SIZE)) >= ETHERFCSL) { len -= ETHERFCSL; } #if DEBUG_LEVEL > 10 { int i; uint8_t *bp = dp->rx_buf_head->rxb_buf; cmn_err(CE_CONT, CONS "%s: len:%d", dp->name, len); for (i = 0; i < 60; i += 10) { cmn_err(CE_CONT, CONS "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", bp[0], bp[1], bp[2], bp[3], bp[4], bp[5], bp[6], bp[7], bp[8], bp[9]); } bp += 10; } #endif return (flag | (len & GEM_RX_LEN)); } static void sfe_tx_desc_init(struct gem_dev *dp, int slot) { uint_t tx_ring_size = dp->gc.gc_tx_ring_size; struct sfe_desc *tdp; uint32_t here; tdp = (void *)&dp->tx_ring[SFE_DESC_SIZE * slot]; /* don't clear d_link field, which have a valid pointer */ tdp->d_cmdsts = 0; /* make a link to this from the previous descriptor */ here = ((uint32_t)dp->tx_ring_dma) + SFE_DESC_SIZE*slot; tdp = (void *) &dp->tx_ring[SFE_DESC_SIZE * SLOT(slot - 1, tx_ring_size)]; tdp->d_link = LE_32(here); } static void sfe_rx_desc_init(struct gem_dev *dp, int slot) { uint_t rx_ring_size = dp->gc.gc_rx_ring_size; struct sfe_desc *rdp; uint32_t here; rdp = (void *)&dp->rx_ring[SFE_DESC_SIZE * slot]; /* don't clear d_link field, which have a valid pointer */ rdp->d_cmdsts = LE_32(CMDSTS_OWN); /* make a link to this from the previous descriptor */ here = ((uint32_t)dp->rx_ring_dma) + SFE_DESC_SIZE*slot; rdp = (void *) &dp->rx_ring[SFE_DESC_SIZE * SLOT(slot - 1, rx_ring_size)]; rdp->d_link = LE_32(here); } static void sfe_tx_desc_clean(struct gem_dev *dp, int slot) { struct sfe_desc *tdp; tdp = (void *)&dp->tx_ring[SFE_DESC_SIZE * slot]; tdp->d_cmdsts = 0; } static void sfe_rx_desc_clean(struct gem_dev *dp, int slot) { struct sfe_desc *rdp; rdp = (void *)&dp->rx_ring[SFE_DESC_SIZE * slot]; rdp->d_cmdsts = LE_32(CMDSTS_OWN); } /* * Device depend interrupt handler */ static uint_t sfe_interrupt(struct gem_dev *dp) { uint_t rx_ring_size = dp->gc.gc_rx_ring_size; uint32_t isr; uint32_t isr_bogus; uint_t flags = 0; boolean_t need_to_reset = B_FALSE; struct sfe_dev *lp = dp->private; /* read reason and clear interrupt */ isr = INL(dp, ISR); isr_bogus = lp->isr_pended; lp->isr_pended = 0; if (((isr | isr_bogus) & lp->our_intr_bits) == 0) { /* we are not the interrupt source */ return (DDI_INTR_UNCLAIMED); } DPRINTF(3, (CE_CONT, CONS "%s: time:%ld %s:called: isr:0x%b rx_active_head: %d", dp->name, ddi_get_lbolt(), __func__, isr, INTR_BITS, dp->rx_active_head)); if (!dp->mac_active) { /* the device is going to stop */ lp->our_intr_bits = 0; return (DDI_INTR_CLAIMED); } isr &= lp->our_intr_bits; if (isr & (ISR_RXSOVR | ISR_RXORN | ISR_RXIDLE | ISR_RXERR | ISR_RXDESC | ISR_RXOK)) { (void) gem_receive(dp); if (isr & (ISR_RXSOVR | ISR_RXORN)) { DPRINTF(0, (CE_CONT, CONS "%s: rx fifo overrun: isr %b", dp->name, isr, INTR_BITS)); /* no need restart rx */ dp->stats.overflow++; } if (isr & ISR_RXIDLE) { DPRINTF(0, (CE_CONT, CONS "%s: rx buffer ran out: isr %b", dp->name, isr, INTR_BITS)); dp->stats.norcvbuf++; /* * Make RXDP points the head of receive * buffer list. */ OUTL(dp, RXDP, dp->rx_ring_dma + SFE_DESC_SIZE * SLOT(dp->rx_active_head, rx_ring_size)); /* Restart the receive engine */ OUTL(dp, CR, lp->cr | CR_RXE); } } if (isr & (ISR_TXURN | ISR_TXERR | ISR_TXDESC | ISR_TXIDLE | ISR_TXOK)) { /* need to reclaim tx buffers */ if (gem_tx_done(dp)) { flags |= INTR_RESTART_TX; } /* * XXX - tx error statistics will be counted in * sfe_tx_desc_stat() and no need to restart tx on errors. */ } if (isr & (ISR_DPERR | ISR_SSERR | ISR_RMABT | ISR_RTABT)) { cmn_err(CE_WARN, "%s: ERROR interrupt: isr %b.", dp->name, isr, INTR_BITS); need_to_reset = B_TRUE; } reset: if (need_to_reset) { (void) gem_restart_nic(dp, GEM_RESTART_KEEP_BUF); flags |= INTR_RESTART_TX; } DPRINTF(5, (CE_CONT, CONS "%s: %s: return: isr: %b", dp->name, __func__, isr, INTR_BITS)); return (DDI_INTR_CLAIMED | flags); } /* ======================================================== */ /* * HW depend MII routine */ /* ======================================================== */ /* * MII routines for NS DP83815 */ static void sfe_mii_sync_dp83815(struct gem_dev *dp) { /* do nothing */ } static uint16_t sfe_mii_read_dp83815(struct gem_dev *dp, uint_t offset) { DPRINTF(4, (CE_CONT, CONS"%s: %s: offset 0x%x", dp->name, __func__, offset)); return ((uint16_t)INL(dp, MII_REGS_BASE + offset*4)); } static void sfe_mii_write_dp83815(struct gem_dev *dp, uint_t offset, uint16_t val) { DPRINTF(4, (CE_CONT, CONS"%s: %s: offset 0x%x 0x%x", dp->name, __func__, offset, val)); OUTL(dp, MII_REGS_BASE + offset*4, val); } static int sfe_mii_config_dp83815(struct gem_dev *dp) { uint32_t srr; srr = INL(dp, SRR) & SRR_REV; DPRINTF(0, (CE_CONT, CONS "%s: srr:0x%04x %04x %04x %04x %04x %04x", dp->name, srr, INW(dp, 0x00cc), /* PGSEL */ INW(dp, 0x00e4), /* PMDCSR */ INW(dp, 0x00fc), /* TSTDAT */ INW(dp, 0x00f4), /* DSPCFG */ INW(dp, 0x00f8))); /* SDCFG */ if (srr == SRR_REV_DP83815CVNG) { /* * NS datasheet says that DP83815CVNG needs following * registers to be patched for optimizing its performance. * A report said that CRC errors on RX disappeared * with the patch. */ OUTW(dp, 0x00cc, 0x0001); /* PGSEL */ OUTW(dp, 0x00e4, 0x189c); /* PMDCSR */ OUTW(dp, 0x00fc, 0x0000); /* TSTDAT */ OUTW(dp, 0x00f4, 0x5040); /* DSPCFG */ OUTW(dp, 0x00f8, 0x008c); /* SDCFG */ OUTW(dp, 0x00cc, 0x0000); /* PGSEL */ DPRINTF(0, (CE_CONT, CONS "%s: PHY patched %04x %04x %04x %04x %04x", dp->name, INW(dp, 0x00cc), /* PGSEL */ INW(dp, 0x00e4), /* PMDCSR */ INW(dp, 0x00fc), /* TSTDAT */ INW(dp, 0x00f4), /* DSPCFG */ INW(dp, 0x00f8))); /* SDCFG */ } else if (((srr ^ SRR_REV_DP83815DVNG) & 0xff00) == 0 || ((srr ^ SRR_REV_DP83816AVNG) & 0xff00) == 0) { /* * Additional packets for later chipset */ OUTW(dp, 0x00cc, 0x0001); /* PGSEL */ OUTW(dp, 0x00e4, 0x189c); /* PMDCSR */ OUTW(dp, 0x00cc, 0x0000); /* PGSEL */ DPRINTF(0, (CE_CONT, CONS "%s: PHY patched %04x %04x", dp->name, INW(dp, 0x00cc), /* PGSEL */ INW(dp, 0x00e4))); /* PMDCSR */ } return (gem_mii_config_default(dp)); } static int sfe_mii_probe_dp83815(struct gem_dev *dp) { uint32_t val; /* try external phy first */ DPRINTF(0, (CE_CONT, CONS "%s: %s: trying external phy", dp->name, __func__)); dp->mii_phy_addr = 0; dp->gc.gc_mii_sync = &sfe_mii_sync_sis900; dp->gc.gc_mii_read = &sfe_mii_read_sis900; dp->gc.gc_mii_write = &sfe_mii_write_sis900; val = INL(dp, CFG) & (CFG_ANEG_SEL | CFG_PHY_CFG); OUTL(dp, CFG, val | CFG_EXT_PHY | CFG_PHY_DIS); if (gem_mii_probe_default(dp) == GEM_SUCCESS) { return (GEM_SUCCESS); } /* switch to internal phy */ DPRINTF(0, (CE_CONT, CONS "%s: %s: switching to internal phy", dp->name, __func__)); dp->mii_phy_addr = -1; dp->gc.gc_mii_sync = &sfe_mii_sync_dp83815; dp->gc.gc_mii_read = &sfe_mii_read_dp83815; dp->gc.gc_mii_write = &sfe_mii_write_dp83815; val = INL(dp, CFG) & (CFG_ANEG_SEL | CFG_PHY_CFG); OUTL(dp, CFG, val | CFG_PAUSE_ADV | CFG_PHY_RST); drv_usecwait(100); /* keep to assert RST bit for a while */ OUTL(dp, CFG, val | CFG_PAUSE_ADV); /* wait for PHY reset */ delay(drv_usectohz(10000)); return (gem_mii_probe_default(dp)); } static int sfe_mii_init_dp83815(struct gem_dev *dp) { uint32_t val; val = INL(dp, CFG) & (CFG_ANEG_SEL | CFG_PHY_CFG); if (dp->mii_phy_addr == -1) { /* select internal phy */ OUTL(dp, CFG, val | CFG_PAUSE_ADV); } else { /* select external phy */ OUTL(dp, CFG, val | CFG_EXT_PHY | CFG_PHY_DIS); } return (GEM_SUCCESS); } /* * MII routines for SiS900 */ #define MDIO_DELAY(dp) {(void) INL(dp, MEAR); (void) INL(dp, MEAR); } static void sfe_mii_sync_sis900(struct gem_dev *dp) { int i; /* send 32 ONE's to make MII line idle */ for (i = 0; i < 32; i++) { OUTL(dp, MEAR, MEAR_MDDIR | MEAR_MDIO); MDIO_DELAY(dp); OUTL(dp, MEAR, MEAR_MDDIR | MEAR_MDIO | MEAR_MDC); MDIO_DELAY(dp); } } static int sfe_mii_config_sis900(struct gem_dev *dp) { struct sfe_dev *lp = dp->private; /* Do chip depend setup */ if ((dp->mii_phy_id & PHY_MASK) == PHY_ICS1893) { /* workaround for ICS1893 PHY */ gem_mii_write(dp, 0x0018, 0xD200); } if (lp->revid == SIS630E_900_REV) { /* * SiS 630E has bugs on default values * of PHY registers */ gem_mii_write(dp, MII_AN_ADVERT, 0x05e1); gem_mii_write(dp, MII_CONFIG1, 0x0022); gem_mii_write(dp, MII_CONFIG2, 0xff00); gem_mii_write(dp, MII_MASK, 0xffc0); } sfe_set_eq_sis630(dp); return (gem_mii_config_default(dp)); } static uint16_t sfe_mii_read_sis900(struct gem_dev *dp, uint_t reg) { uint32_t cmd; uint16_t ret; int i; uint32_t data; cmd = MII_READ_CMD(dp->mii_phy_addr, reg); for (i = 31; i >= 18; i--) { data = ((cmd >> i) & 1) << MEAR_MDIO_SHIFT; OUTL(dp, MEAR, data | MEAR_MDDIR); MDIO_DELAY(dp); OUTL(dp, MEAR, data | MEAR_MDDIR | MEAR_MDC); MDIO_DELAY(dp); } /* turn around cycle */ OUTL(dp, MEAR, 0); MDIO_DELAY(dp); /* get response from PHY */ OUTL(dp, MEAR, MEAR_MDC); MDIO_DELAY(dp); OUTL(dp, MEAR, 0); #if DEBUG_LEBEL > 0 (void) INL(dp, MEAR); /* delay */ if (INL(dp, MEAR) & MEAR_MDIO) { cmn_err(CE_WARN, "%s: PHY@%d not responded", dp->name, dp->mii_phy_addr); } #else MDIO_DELAY(dp); #endif /* terminate response cycle */ OUTL(dp, MEAR, MEAR_MDC); MDIO_DELAY(dp); ret = 0; /* to avoid lint errors */ for (i = 16; i > 0; i--) { OUTL(dp, MEAR, 0); (void) INL(dp, MEAR); /* delay */ ret = (ret << 1) | ((INL(dp, MEAR) >> MEAR_MDIO_SHIFT) & 1); OUTL(dp, MEAR, MEAR_MDC); MDIO_DELAY(dp); } /* send two idle(Z) bits to terminate the read cycle */ for (i = 0; i < 2; i++) { OUTL(dp, MEAR, 0); MDIO_DELAY(dp); OUTL(dp, MEAR, MEAR_MDC); MDIO_DELAY(dp); } return (ret); } static void sfe_mii_write_sis900(struct gem_dev *dp, uint_t reg, uint16_t val) { uint32_t cmd; int i; uint32_t data; cmd = MII_WRITE_CMD(dp->mii_phy_addr, reg, val); for (i = 31; i >= 0; i--) { data = ((cmd >> i) & 1) << MEAR_MDIO_SHIFT; OUTL(dp, MEAR, data | MEAR_MDDIR); MDIO_DELAY(dp); OUTL(dp, MEAR, data | MEAR_MDDIR | MEAR_MDC); MDIO_DELAY(dp); } /* send two idle(Z) bits to terminate the write cycle. */ for (i = 0; i < 2; i++) { OUTL(dp, MEAR, 0); MDIO_DELAY(dp); OUTL(dp, MEAR, MEAR_MDC); MDIO_DELAY(dp); } } #undef MDIO_DELAY static void sfe_set_eq_sis630(struct gem_dev *dp) { uint16_t reg14h; uint16_t eq_value; uint16_t max_value; uint16_t min_value; int i; uint8_t rev; struct sfe_dev *lp = dp->private; rev = lp->revid; if (!(rev == SIS630E_900_REV || rev == SIS630EA1_900_REV || rev == SIS630A_900_REV || rev == SIS630ET_900_REV)) { /* it doesn't have a internal PHY */ return; } if (dp->mii_state == MII_STATE_LINKUP) { reg14h = gem_mii_read(dp, MII_RESV); gem_mii_write(dp, MII_RESV, (0x2200 | reg14h) & 0xBFFF); eq_value = (0x00f8 & gem_mii_read(dp, MII_RESV)) >> 3; max_value = min_value = eq_value; for (i = 1; i < 10; i++) { eq_value = (0x00f8 & gem_mii_read(dp, MII_RESV)) >> 3; max_value = max(eq_value, max_value); min_value = min(eq_value, min_value); } /* for 630E, rule to determine the equalizer value */ if (rev == SIS630E_900_REV || rev == SIS630EA1_900_REV || rev == SIS630ET_900_REV) { if (max_value < 5) { eq_value = max_value; } else if (5 <= max_value && max_value < 15) { eq_value = max(max_value + 1, min_value + 2); } else if (15 <= max_value) { eq_value = max(max_value + 5, min_value + 6); } } /* for 630B0&B1, rule to determine the equalizer value */ else if (rev == SIS630A_900_REV && (lp->bridge_revid == SIS630B0 || lp->bridge_revid == SIS630B1)) { if (max_value == 0) { eq_value = 3; } else { eq_value = (max_value + min_value + 1)/2; } } /* write equalizer value and setting */ reg14h = gem_mii_read(dp, MII_RESV) & ~0x02f8; reg14h |= 0x6000 | (eq_value << 3); gem_mii_write(dp, MII_RESV, reg14h); } else { reg14h = (gem_mii_read(dp, MII_RESV) & ~0x4000) | 0x2000; if (rev == SIS630A_900_REV && (lp->bridge_revid == SIS630B0 || lp->bridge_revid == SIS630B1)) { reg14h |= 0x0200; } gem_mii_write(dp, MII_RESV, reg14h); } } /* ======================================================== */ /* * OS depend (device driver) routine */ /* ======================================================== */ static void sfe_chipinfo_init_sis900(struct gem_dev *dp) { int rev; struct sfe_dev *lp = (struct sfe_dev *)dp->private; rev = lp->revid; if (rev == SIS962_900_REV /* 0x91 */) { /* sis962 or later */ lp->get_mac_addr = &sfe_get_mac_addr_sis962; } else { /* sis900 */ lp->get_mac_addr = &sfe_get_mac_addr_sis900; } lp->bridge_revid = 0; if (rev == SIS630E_900_REV || rev == SIS630EA1_900_REV || rev == SIS630A_900_REV || rev == SIS630ET_900_REV) { /* * read host bridge revision */ dev_info_t *bridge; ddi_acc_handle_t bridge_handle; if ((bridge = sfe_search_pci_dev(0x1039, 0x630)) == NULL) { cmn_err(CE_WARN, "%s: cannot find host bridge (pci1039,630)", dp->name); return; } if (pci_config_setup(bridge, &bridge_handle) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s: pci_config_setup failed", dp->name); return; } lp->bridge_revid = pci_config_get8(bridge_handle, PCI_CONF_REVID); pci_config_teardown(&bridge_handle); } } static int sfe_attach_chip(struct gem_dev *dp) { struct sfe_dev *lp = (struct sfe_dev *)dp->private; DPRINTF(4, (CE_CONT, CONS "!%s: %s called", dp->name, __func__)); /* setup chip-depend get_mac_address function */ if (lp->chip->chip_type == CHIPTYPE_SIS900) { sfe_chipinfo_init_sis900(dp); } else { lp->get_mac_addr = &sfe_get_mac_addr_dp83815; } /* read MAC address */ if (!(lp->get_mac_addr)(dp)) { cmn_err(CE_WARN, "!%s: %s: failed to get factory mac address" " please specify a mac address in sfe.conf", dp->name, __func__); return (GEM_FAILURE); } if (lp->chip->chip_type == CHIPTYPE_DP83815) { dp->mii_phy_addr = -1; /* no need to scan PHY */ dp->misc_flag |= GEM_VLAN_SOFT; dp->txthr += 4; /* VTAG_SIZE */ } dp->txthr = min(dp->txthr, TXFIFOSIZE - 2); return (GEM_SUCCESS); } static int sfeattach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int unit; const char *drv_name; int i; ddi_acc_handle_t conf_handle; uint16_t vid; uint16_t did; uint8_t rev; #ifdef DEBUG_LEVEL uint32_t iline; uint8_t latim; #endif struct chip_info *p; struct gem_dev *dp; struct sfe_dev *lp; caddr_t base; ddi_acc_handle_t regs_ha; struct gem_conf *gcp; unit = ddi_get_instance(dip); drv_name = ddi_driver_name(dip); DPRINTF(3, (CE_CONT, CONS "%s%d: sfeattach: called", drv_name, unit)); /* * Common codes after power-up */ if (pci_config_setup(dip, &conf_handle) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s%d: ddi_regs_map_setup failed", drv_name, unit); goto err; } vid = pci_config_get16(conf_handle, PCI_CONF_VENID); did = pci_config_get16(conf_handle, PCI_CONF_DEVID); rev = pci_config_get16(conf_handle, PCI_CONF_REVID); #ifdef DEBUG_LEVEL iline = pci_config_get32(conf_handle, PCI_CONF_ILINE); latim = pci_config_get8(conf_handle, PCI_CONF_LATENCY_TIMER); #endif #ifdef DEBUG_BUILT_IN_SIS900 rev = SIS630E_900_REV; #endif for (i = 0, p = sfe_chiptbl; i < CHIPTABLESIZE; i++, p++) { if (p->venid == vid && p->devid == did) { /* found */ goto chip_found; } } /* Not found */ cmn_err(CE_WARN, "%s%d: sfe_attach: wrong PCI venid/devid (0x%x, 0x%x)", drv_name, unit, vid, did); pci_config_teardown(&conf_handle); goto err; chip_found: pci_config_put16(conf_handle, PCI_CONF_COMM, PCI_COMM_IO | PCI_COMM_MAE | PCI_COMM_ME | pci_config_get16(conf_handle, PCI_CONF_COMM)); /* ensure D0 mode */ (void) gem_pci_set_power_state(dip, conf_handle, PCI_PMCSR_D0); pci_config_teardown(&conf_handle); switch (cmd) { case DDI_RESUME: return (gem_resume(dip)); case DDI_ATTACH: DPRINTF(0, (CE_CONT, CONS "%s%d: ilr 0x%08x, latency_timer:0x%02x", drv_name, unit, iline, latim)); /* * Map in the device registers. */ if (gem_pci_regs_map_setup(dip, (sfe_use_pcimemspace && p->chip_type == CHIPTYPE_DP83815) ? PCI_ADDR_MEM32 : PCI_ADDR_IO, PCI_ADDR_MASK, &sfe_dev_attr, &base, ®s_ha) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s%d: ddi_regs_map_setup failed", drv_name, unit); goto err; } /* * construct gem configuration */ gcp = kmem_zalloc(sizeof (*gcp), KM_SLEEP); /* name */ (void) sprintf(gcp->gc_name, "%s%d", drv_name, unit); /* consistency on tx and rx */ gcp->gc_tx_buf_align = sizeof (uint8_t) - 1; gcp->gc_tx_max_frags = MAXTXFRAGS; gcp->gc_tx_max_descs_per_pkt = gcp->gc_tx_max_frags; gcp->gc_tx_desc_unit_shift = 4; /* 16 byte */ gcp->gc_tx_buf_size = TX_BUF_SIZE; gcp->gc_tx_buf_limit = gcp->gc_tx_buf_size; gcp->gc_tx_ring_size = TX_RING_SIZE; gcp->gc_tx_ring_limit = gcp->gc_tx_ring_size; gcp->gc_tx_auto_pad = B_TRUE; gcp->gc_tx_copy_thresh = sfe_tx_copy_thresh; gcp->gc_tx_desc_write_oo = B_TRUE; gcp->gc_rx_buf_align = sizeof (uint8_t) - 1; gcp->gc_rx_max_frags = MAXRXFRAGS; gcp->gc_rx_desc_unit_shift = 4; gcp->gc_rx_ring_size = RX_RING_SIZE; gcp->gc_rx_buf_max = RX_BUF_SIZE; gcp->gc_rx_copy_thresh = sfe_rx_copy_thresh; /* map attributes */ gcp->gc_dev_attr = sfe_dev_attr; gcp->gc_buf_attr = sfe_buf_attr; gcp->gc_desc_attr = sfe_buf_attr; /* dma attributes */ gcp->gc_dma_attr_desc = sfe_dma_attr_desc; gcp->gc_dma_attr_txbuf = sfe_dma_attr_buf; gcp->gc_dma_attr_txbuf.dma_attr_align = gcp->gc_tx_buf_align+1; gcp->gc_dma_attr_txbuf.dma_attr_sgllen = gcp->gc_tx_max_frags; gcp->gc_dma_attr_rxbuf = sfe_dma_attr_buf; gcp->gc_dma_attr_rxbuf.dma_attr_align = gcp->gc_rx_buf_align+1; gcp->gc_dma_attr_rxbuf.dma_attr_sgllen = gcp->gc_rx_max_frags; /* time out parameters */ gcp->gc_tx_timeout = 3*ONESEC; gcp->gc_tx_timeout_interval = ONESEC; if (p->chip_type == CHIPTYPE_DP83815) { /* workaround for tx hang */ gcp->gc_tx_timeout_interval = ONESEC/20; /* 50mS */ } /* MII timeout parameters */ gcp->gc_mii_link_watch_interval = ONESEC; gcp->gc_mii_an_watch_interval = ONESEC/5; gcp->gc_mii_reset_timeout = MII_RESET_TIMEOUT; /* 1 sec */ gcp->gc_mii_an_timeout = MII_AN_TIMEOUT; /* 5 sec */ gcp->gc_mii_an_wait = 0; gcp->gc_mii_linkdown_timeout = MII_LINKDOWN_TIMEOUT; /* setting for general PHY */ gcp->gc_mii_an_delay = 0; gcp->gc_mii_linkdown_action = MII_ACTION_RSA; gcp->gc_mii_linkdown_timeout_action = MII_ACTION_RESET; gcp->gc_mii_dont_reset = B_FALSE; /* I/O methods */ /* mac operation */ gcp->gc_attach_chip = &sfe_attach_chip; if (p->chip_type == CHIPTYPE_DP83815) { gcp->gc_reset_chip = &sfe_reset_chip_dp83815; } else { gcp->gc_reset_chip = &sfe_reset_chip_sis900; } gcp->gc_init_chip = &sfe_init_chip; gcp->gc_start_chip = &sfe_start_chip; gcp->gc_stop_chip = &sfe_stop_chip; #ifdef USE_MULTICAST_HASHTBL gcp->gc_multicast_hash = &sfe_mcast_hash; #endif if (p->chip_type == CHIPTYPE_DP83815) { gcp->gc_set_rx_filter = &sfe_set_rx_filter_dp83815; } else { gcp->gc_set_rx_filter = &sfe_set_rx_filter_sis900; } gcp->gc_set_media = &sfe_set_media; gcp->gc_get_stats = &sfe_get_stats; gcp->gc_interrupt = &sfe_interrupt; /* descriptor operation */ gcp->gc_tx_desc_write = &sfe_tx_desc_write; gcp->gc_tx_start = &sfe_tx_start; gcp->gc_rx_desc_write = &sfe_rx_desc_write; gcp->gc_rx_start = NULL; gcp->gc_tx_desc_stat = &sfe_tx_desc_stat; gcp->gc_rx_desc_stat = &sfe_rx_desc_stat; gcp->gc_tx_desc_init = &sfe_tx_desc_init; gcp->gc_rx_desc_init = &sfe_rx_desc_init; gcp->gc_tx_desc_clean = &sfe_tx_desc_clean; gcp->gc_rx_desc_clean = &sfe_rx_desc_clean; /* mii operations */ if (p->chip_type == CHIPTYPE_DP83815) { gcp->gc_mii_probe = &sfe_mii_probe_dp83815; gcp->gc_mii_init = &sfe_mii_init_dp83815; gcp->gc_mii_config = &sfe_mii_config_dp83815; gcp->gc_mii_sync = &sfe_mii_sync_dp83815; gcp->gc_mii_read = &sfe_mii_read_dp83815; gcp->gc_mii_write = &sfe_mii_write_dp83815; gcp->gc_mii_tune_phy = NULL; gcp->gc_flow_control = FLOW_CONTROL_NONE; } else { gcp->gc_mii_probe = &gem_mii_probe_default; gcp->gc_mii_init = NULL; gcp->gc_mii_config = &sfe_mii_config_sis900; gcp->gc_mii_sync = &sfe_mii_sync_sis900; gcp->gc_mii_read = &sfe_mii_read_sis900; gcp->gc_mii_write = &sfe_mii_write_sis900; gcp->gc_mii_tune_phy = &sfe_set_eq_sis630; gcp->gc_flow_control = FLOW_CONTROL_RX_PAUSE; } lp = kmem_zalloc(sizeof (*lp), KM_SLEEP); lp->chip = p; lp->revid = rev; lp->our_intr_bits = 0; lp->isr_pended = 0; cmn_err(CE_CONT, CONS "%s%d: chip:%s rev:0x%02x", drv_name, unit, p->chip_name, rev); dp = gem_do_attach(dip, 0, gcp, base, ®s_ha, lp, sizeof (*lp)); kmem_free(gcp, sizeof (*gcp)); if (dp == NULL) { goto err_freelp; } return (DDI_SUCCESS); err_freelp: kmem_free(lp, sizeof (struct sfe_dev)); err: return (DDI_FAILURE); } return (DDI_FAILURE); } static int sfedetach(dev_info_t *dip, ddi_detach_cmd_t cmd) { switch (cmd) { case DDI_SUSPEND: return (gem_suspend(dip)); case DDI_DETACH: return (gem_do_detach(dip)); } return (DDI_FAILURE); } /* * 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. */ #ifdef __sparc #define sfe_quiesce ddi_quiesce_not_supported #else static int sfe_quiesce(dev_info_t *dip) { struct gem_dev *dp; int ret = 0; dp = GEM_GET_DEV(dip); if (dp == NULL) return (DDI_FAILURE); ret = sfe_stop_chip_quiesce(dp); return (ret); } #endif /* ======================================================== */ /* * OS depend (loadable streams driver) routine */ /* ======================================================== */ DDI_DEFINE_STREAM_OPS(sfe_ops, nulldev, nulldev, sfeattach, sfedetach, nodev, NULL, D_MP, NULL, sfe_quiesce); static struct modldrv modldrv = { &mod_driverops, /* Type of module. This one is a driver */ ident, &sfe_ops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; /* ======================================================== */ /* * Loadable module support */ /* ======================================================== */ int _init(void) { int status; DPRINTF(2, (CE_CONT, CONS "sfe: _init: called")); gem_mod_init(&sfe_ops, "sfe"); status = mod_install(&modlinkage); if (status != DDI_SUCCESS) { gem_mod_fini(&sfe_ops); } return (status); } /* * _fini : done */ int _fini(void) { int status; DPRINTF(2, (CE_CONT, CONS "sfe: _fini: called")); status = mod_remove(&modlinkage); if (status == DDI_SUCCESS) { gem_mod_fini(&sfe_ops); } return (status); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); }