/* * sfe_util.c: general ethernet mac driver framework version 2.6 * * 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. */ #pragma ident "%Z%%M% %I% %E% SMI" /* sfe device driver */ /* * System Header files. */ #include #include #include #include #include #include #include #include #include #include #include /* required for MBLK* */ #include /* required for mionack() */ #include #include #include #include #include #include #include #include #include "sfe_mii.h" #include "sfe_util.h" extern char ident[]; /* Debugging support */ #ifdef GEM_DEBUG_LEVEL static int gem_debug = GEM_DEBUG_LEVEL; #define DPRINTF(n, args) if (gem_debug > (n)) cmn_err args #else #define DPRINTF(n, args) #undef ASSERT #define ASSERT(x) #endif #define IOC_LINESIZE 0x40 /* Is it right for amd64? */ /* * Useful macros and typedefs */ #define ROUNDUP(x, a) (((x) + (a) - 1) & ~((a) - 1)) #define GET_NET16(p) ((((uint8_t *)(p))[0] << 8)| ((uint8_t *)(p))[1]) #define GET_ETHERTYPE(p) GET_NET16(((uint8_t *)(p)) + ETHERADDRL*2) #define GET_IPTYPEv4(p) (((uint8_t *)(p))[sizeof (struct ether_header) + 9]) #define GET_IPTYPEv6(p) (((uint8_t *)(p))[sizeof (struct ether_header) + 6]) #ifndef INT32_MAX #define INT32_MAX 0x7fffffff #endif #define VTAG_OFF (ETHERADDRL*2) #ifndef VTAG_SIZE #define VTAG_SIZE 4 #endif #ifndef VTAG_TPID #define VTAG_TPID 0x8100U #endif #define GET_TXBUF(dp, sn) \ &(dp)->tx_buf[SLOT((dp)->tx_slots_base + (sn), (dp)->gc.gc_tx_buf_size)] #ifndef offsetof #define offsetof(t, m) ((long)&(((t *) 0)->m)) #endif #define TXFLAG_VTAG(flag) \ (((flag) & GEM_TXFLAG_VTAG) >> GEM_TXFLAG_VTAG_SHIFT) #define MAXPKTBUF(dp) \ ((dp)->mtu + sizeof (struct ether_header) + VTAG_SIZE + ETHERFCSL) #define WATCH_INTERVAL_FAST drv_usectohz(100*1000) /* 100mS */ #define BOOLEAN(x) ((x) != 0) /* * Macros to distinct chip generation. */ /* * Private functions */ static void gem_mii_start(struct gem_dev *); static void gem_mii_stop(struct gem_dev *); /* local buffer management */ static void gem_nd_setup(struct gem_dev *dp); static void gem_nd_cleanup(struct gem_dev *dp); static int gem_alloc_memory(struct gem_dev *); static void gem_free_memory(struct gem_dev *); static void gem_init_rx_ring(struct gem_dev *); static void gem_init_tx_ring(struct gem_dev *); __INLINE__ static void gem_append_rxbuf(struct gem_dev *, struct rxbuf *); static void gem_tx_timeout(struct gem_dev *); static void gem_mii_link_watcher(struct gem_dev *dp); static int gem_mac_init(struct gem_dev *dp); static int gem_mac_start(struct gem_dev *dp); static int gem_mac_stop(struct gem_dev *dp, uint_t flags); static void gem_mac_ioctl(struct gem_dev *dp, queue_t *wq, mblk_t *mp); static struct ether_addr gem_etherbroadcastaddr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; int gem_speed_value[] = {10, 100, 1000}; /* ============================================================== */ /* * Misc runtime routines */ /* ============================================================== */ /* * Ether CRC calculation according to 21143 data sheet */ uint32_t gem_ether_crc_le(const uint8_t *addr, int len) { uint32_t crc; CRC32(crc, addr, ETHERADDRL, 0xffffffffU, crc32_table); return (crc); } uint32_t gem_ether_crc_be(const uint8_t *addr, int len) { int idx; int bit; uint_t data; uint32_t crc; #define CRC32_POLY_BE 0x04c11db7 crc = 0xffffffff; for (idx = 0; idx < len; idx++) { for (data = *addr++, bit = 0; bit < 8; bit++, data >>= 1) { crc = (crc << 1) ^ ((((crc >> 31) ^ data) & 1) ? CRC32_POLY_BE : 0); } } return (crc); #undef CRC32_POLY_BE } int gem_prop_get_int(struct gem_dev *dp, char *prop_template, int def_val) { char propname[32]; (void) sprintf(propname, prop_template, dp->name); return (ddi_prop_get_int(DDI_DEV_T_ANY, dp->dip, DDI_PROP_DONTPASS, propname, def_val)); } static int gem_population(uint32_t x) { int i; int cnt; cnt = 0; for (i = 0; i < 32; i++) { if (x & (1 << i)) { cnt++; } } return (cnt); } #ifdef GEM_DEBUG_LEVEL #ifdef GEM_DEBUG_VLAN static void gem_dump_packet(struct gem_dev *dp, char *title, mblk_t *mp, boolean_t check_cksum) { char msg[180]; uint8_t buf[18+20+20]; uint8_t *p; size_t offset; uint_t ethertype; uint_t proto; uint_t ipproto = 0; uint_t iplen; uint_t iphlen; uint_t tcplen; uint_t udplen; uint_t cksum; int rest; int len; char *bp; mblk_t *tp; extern uint_t ip_cksum(mblk_t *, int, uint32_t); msg[0] = 0; bp = msg; rest = sizeof (buf); offset = 0; for (tp = mp; tp; tp = tp->b_cont) { len = tp->b_wptr - tp->b_rptr; len = min(rest, len); bcopy(tp->b_rptr, &buf[offset], len); rest -= len; offset += len; if (rest == 0) { break; } } offset = 0; p = &buf[offset]; /* ethernet address */ sprintf(bp, "ether: %02x:%02x:%02x:%02x:%02x:%02x" " -> %02x:%02x:%02x:%02x:%02x:%02x", p[6], p[7], p[8], p[9], p[10], p[11], p[0], p[1], p[2], p[3], p[4], p[5]); bp = &msg[strlen(msg)]; /* vlag tag and etherrtype */ ethertype = GET_ETHERTYPE(p); if (ethertype == VTAG_TPID) { sprintf(bp, " vtag:0x%04x", GET_NET16(&p[14])); bp = &msg[strlen(msg)]; offset += VTAG_SIZE; p = &buf[offset]; ethertype = GET_ETHERTYPE(p); } sprintf(bp, " type:%04x", ethertype); bp = &msg[strlen(msg)]; /* ethernet packet length */ sprintf(bp, " mblklen:%d", msgdsize(mp)); bp = &msg[strlen(msg)]; if (mp->b_cont) { sprintf(bp, "("); bp = &msg[strlen(msg)]; for (tp = mp; tp; tp = tp->b_cont) { if (tp == mp) { sprintf(bp, "%d", tp->b_wptr - tp->b_rptr); } else { sprintf(bp, "+%d", tp->b_wptr - tp->b_rptr); } bp = &msg[strlen(msg)]; } sprintf(bp, ")"); bp = &msg[strlen(msg)]; } if (ethertype != ETHERTYPE_IP) { goto x; } /* ip address */ offset += sizeof (struct ether_header); p = &buf[offset]; ipproto = p[9]; iplen = GET_NET16(&p[2]); sprintf(bp, ", ip: %d.%d.%d.%d -> %d.%d.%d.%d proto:%d iplen:%d", p[12], p[13], p[14], p[15], p[16], p[17], p[18], p[19], ipproto, iplen); bp = (void *)&msg[strlen(msg)]; iphlen = (p[0] & 0xf) * 4; /* cksum for psuedo header */ cksum = *(uint16_t *)&p[12]; cksum += *(uint16_t *)&p[14]; cksum += *(uint16_t *)&p[16]; cksum += *(uint16_t *)&p[18]; cksum += BE_16(ipproto); /* tcp or udp protocol header */ offset += iphlen; p = &buf[offset]; if (ipproto == IPPROTO_TCP) { tcplen = iplen - iphlen; sprintf(bp, ", tcp: len:%d cksum:%x", tcplen, GET_NET16(&p[16])); bp = (void *)&msg[strlen(msg)]; if (check_cksum) { cksum += BE_16(tcplen); cksum = (uint16_t)ip_cksum(mp, offset, cksum); sprintf(bp, " (%s)", (cksum == 0 || cksum == 0xffff) ? "ok" : "ng"); bp = (void *)&msg[strlen(msg)]; } } else if (ipproto == IPPROTO_UDP) { udplen = GET_NET16(&p[4]); sprintf(bp, ", udp: len:%d cksum:%x", udplen, GET_NET16(&p[6])); bp = (void *)&msg[strlen(msg)]; if (GET_NET16(&p[6]) && check_cksum) { cksum += *(uint16_t *)&p[4]; cksum = (uint16_t)ip_cksum(mp, offset, cksum); sprintf(bp, " (%s)", (cksum == 0 || cksum == 0xffff) ? "ok" : "ng"); bp = (void *)&msg[strlen(msg)]; } } x: cmn_err(CE_CONT, "!%s: %s: %s", dp->name, title, msg); } #endif /* GEM_DEBUG_VLAN */ #endif /* GEM_DEBUG_LEVEL */ /* ============================================================== */ /* * IO cache flush */ /* ============================================================== */ __INLINE__ void gem_rx_desc_dma_sync(struct gem_dev *dp, int head, int nslot, int how) { int n; int m; int rx_desc_unit_shift = dp->gc.gc_rx_desc_unit_shift; /* sync active descriptors */ if (rx_desc_unit_shift < 0 || nslot == 0) { /* no rx descriptor ring */ return; } n = dp->gc.gc_rx_ring_size - head; if ((m = nslot - n) > 0) { (void) ddi_dma_sync(dp->desc_dma_handle, (off_t)0, (size_t)(m << rx_desc_unit_shift), how); nslot = n; } (void) ddi_dma_sync(dp->desc_dma_handle, (off_t)(head << rx_desc_unit_shift), (size_t)(nslot << rx_desc_unit_shift), how); } __INLINE__ void gem_tx_desc_dma_sync(struct gem_dev *dp, int head, int nslot, int how) { int n; int m; int tx_desc_unit_shift = dp->gc.gc_tx_desc_unit_shift; /* sync active descriptors */ if (tx_desc_unit_shift < 0 || nslot == 0) { /* no tx descriptor ring */ return; } n = dp->gc.gc_tx_ring_size - head; if ((m = nslot - n) > 0) { (void) ddi_dma_sync(dp->desc_dma_handle, (off_t)(dp->tx_ring_dma - dp->rx_ring_dma), (size_t)(m << tx_desc_unit_shift), how); nslot = n; } (void) ddi_dma_sync(dp->desc_dma_handle, (off_t)((head << tx_desc_unit_shift) + (dp->tx_ring_dma - dp->rx_ring_dma)), (size_t)(nslot << tx_desc_unit_shift), how); } static void gem_rx_start_default(struct gem_dev *dp, int head, int nslot) { gem_rx_desc_dma_sync(dp, SLOT(head, dp->gc.gc_rx_ring_size), nslot, DDI_DMA_SYNC_FORDEV); } /* ============================================================== */ /* * Buffer management */ /* ============================================================== */ static void gem_dump_txbuf(struct gem_dev *dp, int level, const char *title) { cmn_err(level, "!%s: %s: tx_active: %d[%d] %d[%d] (+%d), " "tx_softq: %d[%d] %d[%d] (+%d), " "tx_free: %d[%d] %d[%d] (+%d), " "tx_desc: %d[%d] %d[%d] (+%d), " "intr: %d[%d] (+%d), ", dp->name, title, dp->tx_active_head, SLOT(dp->tx_active_head, dp->gc.gc_tx_buf_size), dp->tx_active_tail, SLOT(dp->tx_active_tail, dp->gc.gc_tx_buf_size), dp->tx_active_tail - dp->tx_active_head, dp->tx_softq_head, SLOT(dp->tx_softq_head, dp->gc.gc_tx_buf_size), dp->tx_softq_tail, SLOT(dp->tx_softq_tail, dp->gc.gc_tx_buf_size), dp->tx_softq_tail - dp->tx_softq_head, dp->tx_free_head, SLOT(dp->tx_free_head, dp->gc.gc_tx_buf_size), dp->tx_free_tail, SLOT(dp->tx_free_tail, dp->gc.gc_tx_buf_size), dp->tx_free_tail - dp->tx_free_head, dp->tx_desc_head, SLOT(dp->tx_desc_head, dp->gc.gc_tx_ring_size), dp->tx_desc_tail, SLOT(dp->tx_desc_tail, dp->gc.gc_tx_ring_size), dp->tx_desc_tail - dp->tx_desc_head, dp->tx_desc_intr, SLOT(dp->tx_desc_intr, dp->gc.gc_tx_ring_size), dp->tx_desc_intr - dp->tx_desc_head); } static void gem_free_rxbuf(struct rxbuf *rbp) { struct gem_dev *dp; dp = rbp->rxb_devp; ASSERT(mutex_owned(&dp->intrlock)); rbp->rxb_next = dp->rx_buf_freelist; dp->rx_buf_freelist = rbp; dp->rx_buf_freecnt++; } /* * gem_get_rxbuf: supply a receive buffer which have been mapped into * DMA space. */ struct rxbuf * gem_get_rxbuf(struct gem_dev *dp, int cansleep) { struct rxbuf *rbp; uint_t count = 0; int i; int err; ASSERT(mutex_owned(&dp->intrlock)); DPRINTF(3, (CE_CONT, "!gem_get_rxbuf: called freecnt:%d", dp->rx_buf_freecnt)); /* * Get rx buffer management structure */ rbp = dp->rx_buf_freelist; if (rbp) { /* get one from the recycle list */ ASSERT(dp->rx_buf_freecnt > 0); dp->rx_buf_freelist = rbp->rxb_next; dp->rx_buf_freecnt--; rbp->rxb_next = NULL; return (rbp); } /* * Allocate a rx buffer management structure */ rbp = kmem_zalloc(sizeof (*rbp), cansleep ? KM_SLEEP : KM_NOSLEEP); if (rbp == NULL) { /* no memory */ return (NULL); } /* * Prepare a back pointer to the device structure which will be * refered on freeing the buffer later. */ rbp->rxb_devp = dp; /* allocate a dma handle for rx data buffer */ if ((err = ddi_dma_alloc_handle(dp->dip, &dp->gc.gc_dma_attr_rxbuf, (cansleep ? DDI_DMA_SLEEP : DDI_DMA_DONTWAIT), NULL, &rbp->rxb_dh)) != DDI_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: ddi_dma_alloc_handle:1 failed, err=%d", dp->name, __func__, err); kmem_free(rbp, sizeof (struct rxbuf)); return (NULL); } /* allocate a bounce buffer for rx */ if ((err = ddi_dma_mem_alloc(rbp->rxb_dh, ROUNDUP(dp->rx_buf_len, IOC_LINESIZE), &dp->gc.gc_buf_attr, /* * if the nic requires a header at the top of receive buffers, * it may access the rx buffer randomly. */ (dp->gc.gc_rx_header_len > 0) ? DDI_DMA_CONSISTENT : DDI_DMA_STREAMING, cansleep ? DDI_DMA_SLEEP : DDI_DMA_DONTWAIT, NULL, &rbp->rxb_buf, &rbp->rxb_buf_len, &rbp->rxb_bah)) != DDI_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: ddi_dma_mem_alloc: failed, err=%d", dp->name, __func__, err); ddi_dma_free_handle(&rbp->rxb_dh); kmem_free(rbp, sizeof (struct rxbuf)); return (NULL); } /* Mapin the bounce buffer into the DMA space */ if ((err = ddi_dma_addr_bind_handle(rbp->rxb_dh, NULL, rbp->rxb_buf, dp->rx_buf_len, ((dp->gc.gc_rx_header_len > 0) ?(DDI_DMA_RDWR | DDI_DMA_CONSISTENT) :(DDI_DMA_READ | DDI_DMA_STREAMING)), cansleep ? DDI_DMA_SLEEP : DDI_DMA_DONTWAIT, NULL, rbp->rxb_dmacookie, &count)) != DDI_DMA_MAPPED) { ASSERT(err != DDI_DMA_INUSE); DPRINTF(0, (CE_WARN, "!%s: ddi_dma_addr_bind_handle: failed, err=%d", dp->name, __func__, err)); /* * we failed to allocate a dma resource * for the rx bounce buffer. */ ddi_dma_mem_free(&rbp->rxb_bah); ddi_dma_free_handle(&rbp->rxb_dh); kmem_free(rbp, sizeof (struct rxbuf)); return (NULL); } /* correct the rest of the DMA mapping */ for (i = 1; i < count; i++) { ddi_dma_nextcookie(rbp->rxb_dh, &rbp->rxb_dmacookie[i]); } rbp->rxb_nfrags = count; /* Now we successfully prepared an rx buffer */ dp->rx_buf_allocated++; return (rbp); } /* ============================================================== */ /* * memory resource management */ /* ============================================================== */ static int gem_alloc_memory(struct gem_dev *dp) { caddr_t ring; caddr_t buf; size_t req_size; size_t ring_len; size_t buf_len; ddi_dma_cookie_t ring_cookie; ddi_dma_cookie_t buf_cookie; uint_t count; int i; int err; struct txbuf *tbp; int tx_buf_len; ddi_dma_attr_t dma_attr_txbounce; DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); dp->desc_dma_handle = NULL; req_size = dp->rx_desc_size + dp->tx_desc_size + dp->gc.gc_io_area_size; if (req_size > 0) { /* * Alloc RX/TX descriptors and a io area. */ if ((err = ddi_dma_alloc_handle(dp->dip, &dp->gc.gc_dma_attr_desc, DDI_DMA_SLEEP, NULL, &dp->desc_dma_handle)) != DDI_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: ddi_dma_alloc_handle failed: %d", dp->name, __func__, err); return (ENOMEM); } if ((err = ddi_dma_mem_alloc(dp->desc_dma_handle, req_size, &dp->gc.gc_desc_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &ring, &ring_len, &dp->desc_acc_handle)) != DDI_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: ddi_dma_mem_alloc failed: " "ret %d, request size: %d", dp->name, __func__, err, (int)req_size); ddi_dma_free_handle(&dp->desc_dma_handle); return (ENOMEM); } if ((err = ddi_dma_addr_bind_handle(dp->desc_dma_handle, NULL, ring, ring_len, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &ring_cookie, &count)) != DDI_SUCCESS) { ASSERT(err != DDI_DMA_INUSE); cmn_err(CE_WARN, "!%s: %s: ddi_dma_addr_bind_handle failed: %d", dp->name, __func__, err); ddi_dma_mem_free(&dp->desc_acc_handle); ddi_dma_free_handle(&dp->desc_dma_handle); return (ENOMEM); } ASSERT(count == 1); /* set base of rx descriptor ring */ dp->rx_ring = ring; dp->rx_ring_dma = ring_cookie.dmac_laddress; /* set base of tx descriptor ring */ dp->tx_ring = dp->rx_ring + dp->rx_desc_size; dp->tx_ring_dma = dp->rx_ring_dma + dp->rx_desc_size; /* set base of io area */ dp->io_area = dp->tx_ring + dp->tx_desc_size; dp->io_area_dma = dp->tx_ring_dma + dp->tx_desc_size; } /* * Prepare DMA resources for tx packets */ ASSERT(dp->gc.gc_tx_buf_size > 0); /* Special dma attribute for tx bounce buffers */ dma_attr_txbounce = dp->gc.gc_dma_attr_txbuf; dma_attr_txbounce.dma_attr_sgllen = 1; dma_attr_txbounce.dma_attr_align = max(dma_attr_txbounce.dma_attr_align, IOC_LINESIZE); /* Size for tx bounce buffers must be max tx packet size. */ tx_buf_len = MAXPKTBUF(dp); tx_buf_len = ROUNDUP(tx_buf_len, IOC_LINESIZE); ASSERT(tx_buf_len >= ETHERMAX+ETHERFCSL); for (i = 0, tbp = dp->tx_buf; i < dp->gc.gc_tx_buf_size; i++, tbp++) { /* setup bounce buffers for tx packets */ if ((err = ddi_dma_alloc_handle(dp->dip, &dma_attr_txbounce, DDI_DMA_SLEEP, NULL, &tbp->txb_bdh)) != DDI_SUCCESS) { cmn_err(CE_WARN, "!%s: %s ddi_dma_alloc_handle for bounce buffer failed:" " err=%d, i=%d", dp->name, __func__, err, i); goto err_alloc_dh; } if ((err = ddi_dma_mem_alloc(tbp->txb_bdh, tx_buf_len, &dp->gc.gc_buf_attr, DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL, &buf, &buf_len, &tbp->txb_bah)) != DDI_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: ddi_dma_mem_alloc for bounce buffer failed" "ret %d, request size %d", dp->name, __func__, err, tx_buf_len); ddi_dma_free_handle(&tbp->txb_bdh); goto err_alloc_dh; } if ((err = ddi_dma_addr_bind_handle(tbp->txb_bdh, NULL, buf, buf_len, DDI_DMA_WRITE | DDI_DMA_STREAMING, DDI_DMA_SLEEP, NULL, &buf_cookie, &count)) != DDI_SUCCESS) { ASSERT(err != DDI_DMA_INUSE); cmn_err(CE_WARN, "!%s: %s: ddi_dma_addr_bind_handle for bounce buffer failed: %d", dp->name, __func__, err); ddi_dma_mem_free(&tbp->txb_bah); ddi_dma_free_handle(&tbp->txb_bdh); goto err_alloc_dh; } ASSERT(count == 1); tbp->txb_buf = buf; tbp->txb_buf_dma = buf_cookie.dmac_laddress; } return (0); err_alloc_dh: if (dp->gc.gc_tx_buf_size > 0) { while (i-- > 0) { (void) ddi_dma_unbind_handle(dp->tx_buf[i].txb_bdh); ddi_dma_mem_free(&dp->tx_buf[i].txb_bah); ddi_dma_free_handle(&dp->tx_buf[i].txb_bdh); } } if (dp->desc_dma_handle) { (void) ddi_dma_unbind_handle(dp->desc_dma_handle); ddi_dma_mem_free(&dp->desc_acc_handle); ddi_dma_free_handle(&dp->desc_dma_handle); dp->desc_dma_handle = NULL; } return (ENOMEM); } static void gem_free_memory(struct gem_dev *dp) { int i; struct rxbuf *rbp; struct txbuf *tbp; DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); /* Free TX/RX descriptors and tx padding buffer */ if (dp->desc_dma_handle) { (void) ddi_dma_unbind_handle(dp->desc_dma_handle); ddi_dma_mem_free(&dp->desc_acc_handle); ddi_dma_free_handle(&dp->desc_dma_handle); dp->desc_dma_handle = NULL; } /* Free dma handles for Tx */ for (i = dp->gc.gc_tx_buf_size, tbp = dp->tx_buf; i--; tbp++) { /* Free bounce buffer associated to each txbuf */ (void) ddi_dma_unbind_handle(tbp->txb_bdh); ddi_dma_mem_free(&tbp->txb_bah); ddi_dma_free_handle(&tbp->txb_bdh); } /* Free rx buffer */ while ((rbp = dp->rx_buf_freelist) != NULL) { ASSERT(dp->rx_buf_freecnt > 0); dp->rx_buf_freelist = rbp->rxb_next; dp->rx_buf_freecnt--; /* release DMA mapping */ ASSERT(rbp->rxb_dh != NULL); /* free dma handles for rx bbuf */ /* it has dma mapping always */ ASSERT(rbp->rxb_nfrags > 0); (void) ddi_dma_unbind_handle(rbp->rxb_dh); /* free the associated bounce buffer and dma handle */ ASSERT(rbp->rxb_bah != NULL); ddi_dma_mem_free(&rbp->rxb_bah); /* free the associated dma handle */ ddi_dma_free_handle(&rbp->rxb_dh); /* free the base memory of rx buffer management */ kmem_free(rbp, sizeof (struct rxbuf)); } } /* ============================================================== */ /* * Rx/Tx descriptor slot management */ /* ============================================================== */ /* * Initialize an empty rx ring. */ static void gem_init_rx_ring(struct gem_dev *dp) { int i; int rx_ring_size = dp->gc.gc_rx_ring_size; DPRINTF(1, (CE_CONT, "!%s: %s ring_size:%d, buf_max:%d", dp->name, __func__, rx_ring_size, dp->gc.gc_rx_buf_max)); /* make a physical chain of rx descriptors */ for (i = 0; i < rx_ring_size; i++) { (*dp->gc.gc_rx_desc_init)(dp, i); } gem_rx_desc_dma_sync(dp, 0, rx_ring_size, DDI_DMA_SYNC_FORDEV); dp->rx_active_head = (seqnum_t)0; dp->rx_active_tail = (seqnum_t)0; ASSERT(dp->rx_buf_head == (struct rxbuf *)NULL); ASSERT(dp->rx_buf_tail == (struct rxbuf *)NULL); } /* * Prepare rx buffers and put them into the rx buffer/descriptor ring. */ static void gem_prepare_rx_buf(struct gem_dev *dp) { int i; int nrbuf; struct rxbuf *rbp; ASSERT(mutex_owned(&dp->intrlock)); /* Now we have no active buffers in rx ring */ nrbuf = min(dp->gc.gc_rx_ring_size, dp->gc.gc_rx_buf_max); for (i = 0; i < nrbuf; i++) { if ((rbp = gem_get_rxbuf(dp, B_TRUE)) == NULL) { break; } gem_append_rxbuf(dp, rbp); } gem_rx_desc_dma_sync(dp, 0, dp->gc.gc_rx_ring_size, DDI_DMA_SYNC_FORDEV); } /* * Reclaim active rx buffers in rx buffer ring. */ static void gem_clean_rx_buf(struct gem_dev *dp) { int i; struct rxbuf *rbp; int rx_ring_size = dp->gc.gc_rx_ring_size; #ifdef GEM_DEBUG_LEVEL int total; #endif ASSERT(mutex_owned(&dp->intrlock)); DPRINTF(2, (CE_CONT, "!%s: %s: %d buffers are free", dp->name, __func__, dp->rx_buf_freecnt)); /* * clean up HW descriptors */ for (i = 0; i < rx_ring_size; i++) { (*dp->gc.gc_rx_desc_clean)(dp, i); } gem_rx_desc_dma_sync(dp, 0, rx_ring_size, DDI_DMA_SYNC_FORDEV); #ifdef GEM_DEBUG_LEVEL total = 0; #endif /* * Reclaim allocated rx buffers */ while ((rbp = dp->rx_buf_head) != NULL) { #ifdef GEM_DEBUG_LEVEL total++; #endif /* remove the first one from rx buffer list */ dp->rx_buf_head = rbp->rxb_next; /* recycle the rxbuf */ gem_free_rxbuf(rbp); } dp->rx_buf_tail = (struct rxbuf *)NULL; DPRINTF(2, (CE_CONT, "!%s: %s: %d buffers freeed, total: %d free", dp->name, __func__, total, dp->rx_buf_freecnt)); } /* * Initialize an empty transmit buffer/descriptor ring */ static void gem_init_tx_ring(struct gem_dev *dp) { int i; int tx_buf_size = dp->gc.gc_tx_buf_size; int tx_ring_size = dp->gc.gc_tx_ring_size; DPRINTF(2, (CE_CONT, "!%s: %s: ring_size:%d, buf_size:%d", dp->name, __func__, dp->gc.gc_tx_ring_size, dp->gc.gc_tx_buf_size)); ASSERT(!dp->mac_active); /* initialize active list and free list */ dp->tx_slots_base = SLOT(dp->tx_slots_base + dp->tx_softq_head, tx_buf_size); dp->tx_softq_tail -= dp->tx_softq_head; dp->tx_softq_head = (seqnum_t)0; dp->tx_active_head = dp->tx_softq_head; dp->tx_active_tail = dp->tx_softq_head; dp->tx_free_head = dp->tx_softq_tail; dp->tx_free_tail = dp->gc.gc_tx_buf_limit; dp->tx_desc_head = (seqnum_t)0; dp->tx_desc_tail = (seqnum_t)0; dp->tx_desc_intr = (seqnum_t)0; for (i = 0; i < tx_ring_size; i++) { (*dp->gc.gc_tx_desc_init)(dp, i); } gem_tx_desc_dma_sync(dp, 0, tx_ring_size, DDI_DMA_SYNC_FORDEV); } __INLINE__ static void gem_txbuf_free_dma_resources(struct txbuf *tbp) { if (tbp->txb_mp) { freemsg(tbp->txb_mp); tbp->txb_mp = NULL; } tbp->txb_nfrags = 0; tbp->txb_flag = 0; } #pragma inline(gem_txbuf_free_dma_resources) /* * reclaim active tx buffers and reset positions in tx rings. */ static void gem_clean_tx_buf(struct gem_dev *dp) { int i; seqnum_t head; seqnum_t tail; seqnum_t sn; struct txbuf *tbp; int tx_ring_size = dp->gc.gc_tx_ring_size; #ifdef GEM_DEBUG_LEVEL int err; #endif ASSERT(!dp->mac_active); ASSERT(dp->tx_busy == 0); ASSERT(dp->tx_softq_tail == dp->tx_free_head); /* * clean up all HW descriptors */ for (i = 0; i < tx_ring_size; i++) { (*dp->gc.gc_tx_desc_clean)(dp, i); } gem_tx_desc_dma_sync(dp, 0, tx_ring_size, DDI_DMA_SYNC_FORDEV); /* dequeue all active and loaded buffers */ head = dp->tx_active_head; tail = dp->tx_softq_tail; ASSERT(dp->tx_free_head - head >= 0); tbp = GET_TXBUF(dp, head); for (sn = head; sn != tail; sn++) { gem_txbuf_free_dma_resources(tbp); ASSERT(tbp->txb_mp == NULL); dp->stats.errxmt++; tbp = tbp->txb_next; } #ifdef GEM_DEBUG_LEVEL /* ensure no dma resources for tx are not in use now */ err = 0; while (sn != head + dp->gc.gc_tx_buf_size) { if (tbp->txb_mp || tbp->txb_nfrags) { DPRINTF(0, (CE_CONT, "%s: %s: sn:%d[%d] mp:%p nfrags:%d", dp->name, __func__, sn, SLOT(sn, dp->gc.gc_tx_buf_size), tbp->txb_mp, tbp->txb_nfrags)); err = 1; } sn++; tbp = tbp->txb_next; } if (err) { gem_dump_txbuf(dp, CE_WARN, "gem_clean_tx_buf: tbp->txb_mp != NULL"); } #endif /* recycle buffers, now no active tx buffers in the ring */ dp->tx_free_tail += tail - head; ASSERT(dp->tx_free_tail == dp->tx_free_head + dp->gc.gc_tx_buf_limit); /* fix positions in tx buffer rings */ dp->tx_active_head = dp->tx_free_head; dp->tx_active_tail = dp->tx_free_head; dp->tx_softq_head = dp->tx_free_head; dp->tx_softq_tail = dp->tx_free_head; } /* * Reclaim transmitted buffers from tx buffer/descriptor ring. */ __INLINE__ int gem_reclaim_txbuf(struct gem_dev *dp) { struct txbuf *tbp; uint_t txstat; int err = GEM_SUCCESS; seqnum_t head; seqnum_t tail; seqnum_t sn; seqnum_t desc_head; int tx_ring_size = dp->gc.gc_tx_ring_size; uint_t (*tx_desc_stat)(struct gem_dev *dp, int slot, int ndesc) = dp->gc.gc_tx_desc_stat; clock_t now; now = ddi_get_lbolt(); if (now == (clock_t)0) { /* make non-zero timestamp */ now--; } mutex_enter(&dp->xmitlock); head = dp->tx_active_head; tail = dp->tx_active_tail; #if GEM_DEBUG_LEVEL > 2 if (head != tail) { cmn_err(CE_CONT, "!%s: %s: " "testing active_head:%d[%d], active_tail:%d[%d]", dp->name, __func__, head, SLOT(head, dp->gc.gc_tx_buf_size), tail, SLOT(tail, dp->gc.gc_tx_buf_size)); } #endif #ifdef DEBUG if (dp->tx_reclaim_busy == 0) { /* check tx buffer management consistency */ ASSERT(dp->tx_free_tail - dp->tx_active_head == dp->gc.gc_tx_buf_limit); /* EMPTY */ } #endif dp->tx_reclaim_busy++; /* sync all active HW descriptors */ gem_tx_desc_dma_sync(dp, SLOT(dp->tx_desc_head, tx_ring_size), dp->tx_desc_tail - dp->tx_desc_head, DDI_DMA_SYNC_FORKERNEL); tbp = GET_TXBUF(dp, head); desc_head = dp->tx_desc_head; for (sn = head; sn != tail; dp->tx_active_head = (++sn), tbp = tbp->txb_next) { int ndescs; ASSERT(tbp->txb_desc == desc_head); ndescs = tbp->txb_ndescs; if (ndescs == 0) { /* skip errored descriptors */ continue; } txstat = (*tx_desc_stat)(dp, SLOT(tbp->txb_desc, tx_ring_size), ndescs); if (txstat == 0) { /* not transmitted yet */ break; } if (!dp->tx_blocked && (tbp->txb_flag & GEM_TXFLAG_INTR)) { dp->tx_blocked = now; } ASSERT(txstat & (GEM_TX_DONE | GEM_TX_ERR)); if (txstat & GEM_TX_ERR) { err = GEM_FAILURE; cmn_err(CE_WARN, "!%s: tx error at desc %d[%d]", dp->name, sn, SLOT(sn, tx_ring_size)); } #if GEM_DEBUG_LEVEL > 4 if (now - tbp->txb_stime >= 50) { cmn_err(CE_WARN, "!%s: tx delay while %d mS", dp->name, (now - tbp->txb_stime)*10); } #endif /* free transmitted descriptors */ desc_head += ndescs; } if (dp->tx_desc_head != desc_head) { /* we have reclaimed one or more tx buffers */ dp->tx_desc_head = desc_head; /* If we passed the next interrupt position, update it */ if (desc_head - dp->tx_desc_intr > 0) { dp->tx_desc_intr = desc_head; } } mutex_exit(&dp->xmitlock); /* free dma mapping resources associated with transmitted tx buffers */ tbp = GET_TXBUF(dp, head); tail = sn; #if GEM_DEBUG_LEVEL > 2 if (head != tail) { cmn_err(CE_CONT, "%s: freeing head:%d[%d], tail:%d[%d]", __func__, head, SLOT(head, dp->gc.gc_tx_buf_size), tail, SLOT(tail, dp->gc.gc_tx_buf_size)); } #endif for (sn = head; sn != tail; sn++, tbp = tbp->txb_next) { gem_txbuf_free_dma_resources(tbp); } /* recycle the tx buffers */ mutex_enter(&dp->xmitlock); if (--dp->tx_reclaim_busy == 0) { /* we are the last thread who can update free tail */ #if GEM_DEBUG_LEVEL > 4 /* check all resouces have been deallocated */ sn = dp->tx_free_tail; tbp = GET_TXBUF(dp, new_tail); while (sn != dp->tx_active_head + dp->gc.gc_tx_buf_limit) { if (tbp->txb_nfrags) { /* in use */ break; } ASSERT(tbp->txb_mp == NULL); tbp = tbp->txb_next; sn++; } ASSERT(dp->tx_active_head + dp->gc.gc_tx_buf_limit == sn); #endif dp->tx_free_tail = dp->tx_active_head + dp->gc.gc_tx_buf_limit; } if (!dp->mac_active) { /* someone may be waiting for me. */ cv_broadcast(&dp->tx_drain_cv); } #if GEM_DEBUG_LEVEL > 2 cmn_err(CE_CONT, "!%s: %s: called, " "free_head:%d free_tail:%d(+%d) added:%d", dp->name, __func__, dp->tx_free_head, dp->tx_free_tail, dp->tx_free_tail - dp->tx_free_head, tail - head); #endif mutex_exit(&dp->xmitlock); return (err); } #pragma inline(gem_reclaim_txbuf) /* * Make tx descriptors in out-of-order manner */ static void gem_tx_load_descs_oo(struct gem_dev *dp, seqnum_t start_slot, seqnum_t end_slot, uint64_t flags) { seqnum_t sn; struct txbuf *tbp; int tx_ring_size = dp->gc.gc_tx_ring_size; int (*tx_desc_write) (struct gem_dev *dp, int slot, ddi_dma_cookie_t *dmacookie, int frags, uint64_t flag) = dp->gc.gc_tx_desc_write; clock_t now = ddi_get_lbolt(); sn = start_slot; tbp = GET_TXBUF(dp, sn); do { #if GEM_DEBUG_LEVEL > 1 if (dp->tx_cnt < 100) { dp->tx_cnt++; flags |= GEM_TXFLAG_INTR; } #endif /* write a tx descriptor */ tbp->txb_desc = sn; tbp->txb_ndescs = (*tx_desc_write)(dp, SLOT(sn, tx_ring_size), tbp->txb_dmacookie, tbp->txb_nfrags, flags | tbp->txb_flag); tbp->txb_stime = now; ASSERT(tbp->txb_ndescs == 1); flags = 0; sn++; tbp = tbp->txb_next; } while (sn != end_slot); } __INLINE__ static size_t gem_setup_txbuf_copy(struct gem_dev *dp, mblk_t *mp, struct txbuf *tbp) { size_t min_pkt; caddr_t bp; size_t off; mblk_t *tp; size_t len; uint64_t flag; ASSERT(tbp->txb_mp == NULL); /* we use bounce buffer for the packet */ min_pkt = ETHERMIN; bp = tbp->txb_buf; off = 0; tp = mp; flag = tbp->txb_flag; if (flag & GEM_TXFLAG_SWVTAG) { /* need to increase min packet size */ min_pkt += VTAG_SIZE; ASSERT((flag & GEM_TXFLAG_VTAG) == 0); } /* copy the rest */ for (; tp; tp = tp->b_cont) { if ((len = (long)tp->b_wptr - (long)tp->b_rptr) > 0) { bcopy(tp->b_rptr, &bp[off], len); off += len; } } if (off < min_pkt && (min_pkt > ETHERMIN || !dp->gc.gc_tx_auto_pad)) { /* * Extend the packet to minimum packet size explicitly. * For software vlan packets, we shouldn't use tx autopad * function because nics may not be aware of vlan. * we must keep 46 octet of payload even if we use vlan. */ bzero(&bp[off], min_pkt - off); off = min_pkt; } (void) ddi_dma_sync(tbp->txb_bdh, (off_t)0, off, DDI_DMA_SYNC_FORDEV); tbp->txb_dmacookie[0].dmac_laddress = tbp->txb_buf_dma; tbp->txb_dmacookie[0].dmac_size = off; DPRINTF(2, (CE_CONT, "!%s: %s: copy: addr:0x%llx len:0x%x, vtag:0x%04x, min_pkt:%d", dp->name, __func__, tbp->txb_dmacookie[0].dmac_laddress, tbp->txb_dmacookie[0].dmac_size, (flag & GEM_TXFLAG_VTAG) >> GEM_TXFLAG_VTAG_SHIFT, min_pkt)); /* save misc info */ tbp->txb_mp = mp; tbp->txb_nfrags = 1; #ifdef DEBUG_MULTIFRAGS if (dp->gc.gc_tx_max_frags >= 3 && tbp->txb_dmacookie[0].dmac_size > 16*3) { tbp->txb_dmacookie[1].dmac_laddress = tbp->txb_dmacookie[0].dmac_laddress + 16; tbp->txb_dmacookie[2].dmac_laddress = tbp->txb_dmacookie[1].dmac_laddress + 16; tbp->txb_dmacookie[2].dmac_size = tbp->txb_dmacookie[0].dmac_size - 16*2; tbp->txb_dmacookie[1].dmac_size = 16; tbp->txb_dmacookie[0].dmac_size = 16; tbp->txb_nfrags = 3; } #endif return (off); } #pragma inline(gem_setup_txbuf_copy) __INLINE__ static void gem_tx_start_unit(struct gem_dev *dp) { seqnum_t head; seqnum_t tail; struct txbuf *tbp_head; struct txbuf *tbp_tail; /* update HW descriptors from soft queue */ ASSERT(mutex_owned(&dp->xmitlock)); ASSERT(dp->tx_softq_head == dp->tx_active_tail); head = dp->tx_softq_head; tail = dp->tx_softq_tail; DPRINTF(1, (CE_CONT, "%s: %s: called, softq %d %d[+%d], desc %d %d[+%d]", dp->name, __func__, head, tail, tail - head, dp->tx_desc_head, dp->tx_desc_tail, dp->tx_desc_tail - dp->tx_desc_head)); ASSERT(tail - head > 0); dp->tx_desc_tail = tail; tbp_head = GET_TXBUF(dp, head); tbp_tail = GET_TXBUF(dp, tail - 1); ASSERT(tbp_tail->txb_desc + tbp_tail->txb_ndescs == dp->tx_desc_tail); dp->gc.gc_tx_start(dp, SLOT(tbp_head->txb_desc, dp->gc.gc_tx_ring_size), tbp_tail->txb_desc + tbp_tail->txb_ndescs - tbp_head->txb_desc); /* advance softq head and active tail */ dp->tx_softq_head = dp->tx_active_tail = tail; } #pragma inline(gem_tx_start_unit) #ifdef GEM_DEBUG_LEVEL static int gem_send_cnt[10]; #endif #define PKT_MIN_SIZE (sizeof (struct ether_header) + 10 + VTAG_SIZE) #define EHLEN (sizeof (struct ether_header)) /* * check ether packet type and ip protocol */ static uint64_t gem_txbuf_options(struct gem_dev *dp, mblk_t *mp, uint8_t *bp) { mblk_t *tp; ssize_t len; uint_t vtag; int off; uint64_t flag; flag = 0ULL; /* * prepare continuous header of the packet for protocol analysis */ if ((long)mp->b_wptr - (long)mp->b_rptr < PKT_MIN_SIZE) { /* we use work buffer to copy mblk */ for (tp = mp, off = 0; tp && (off < PKT_MIN_SIZE); tp = tp->b_cont, off += len) { len = (long)tp->b_wptr - (long)tp->b_rptr; len = min(len, PKT_MIN_SIZE - off); bcopy(tp->b_rptr, &bp[off], len); } } else { /* we can use mblk without copy */ bp = mp->b_rptr; } /* process vlan tag for GLD v3 */ if (GET_NET16(&bp[VTAG_OFF]) == VTAG_TPID) { if (dp->misc_flag & GEM_VLAN_HARD) { vtag = GET_NET16(&bp[VTAG_OFF + 2]); ASSERT(vtag); flag |= vtag << GEM_TXFLAG_VTAG_SHIFT; } else { flag |= GEM_TXFLAG_SWVTAG; } } return (flag); } #undef EHLEN #undef PKT_MIN_SIZE /* * gem_send_common is an exported function because hw depend routines may * use it for sending control frames like setup frames for 2114x chipset. */ mblk_t * gem_send_common(struct gem_dev *dp, mblk_t *mp_head, uint32_t flags) { int nmblk; int avail; mblk_t *tp; mblk_t *mp; int i; struct txbuf *tbp; seqnum_t head; uint64_t load_flags; uint64_t len_total = 0; uint32_t bcast = 0; uint32_t mcast = 0; ASSERT(mp_head != NULL); mp = mp_head; nmblk = 1; while ((mp = mp->b_next) != NULL) { nmblk++; } #ifdef GEM_DEBUG_LEVEL gem_send_cnt[0]++; gem_send_cnt[min(nmblk, 9)]++; #endif /* * Aquire resources */ mutex_enter(&dp->xmitlock); if (dp->mac_suspended) { mutex_exit(&dp->xmitlock); mp = mp_head; while (mp) { tp = mp->b_next; freemsg(mp); mp = tp; } return (NULL); } if (!dp->mac_active && (flags & GEM_SEND_CTRL) == 0) { /* don't send data packets while mac isn't active */ /* XXX - should we discard packets? */ mutex_exit(&dp->xmitlock); return (mp_head); } /* allocate free slots */ head = dp->tx_free_head; avail = dp->tx_free_tail - head; DPRINTF(2, (CE_CONT, "!%s: %s: called, free_head:%d free_tail:%d(+%d) req:%d", dp->name, __func__, dp->tx_free_head, dp->tx_free_tail, avail, nmblk)); avail = min(avail, dp->tx_max_packets); if (nmblk > avail) { if (avail == 0) { /* no resources; short cut */ DPRINTF(2, (CE_CONT, "!%s: no resources", __func__)); dp->tx_max_packets = max(dp->tx_max_packets - 1, 1); goto done; } nmblk = avail; } dp->tx_free_head = head + nmblk; load_flags = ((dp->tx_busy++) == 0) ? GEM_TXFLAG_HEAD : 0; /* update last interrupt position if tx buffers exhaust. */ if (nmblk == avail) { tbp = GET_TXBUF(dp, head + avail - 1); tbp->txb_flag = GEM_TXFLAG_INTR; dp->tx_desc_intr = head + avail; } mutex_exit(&dp->xmitlock); tbp = GET_TXBUF(dp, head); for (i = nmblk; i > 0; i--, tbp = tbp->txb_next) { uint8_t *bp; uint64_t txflag; /* remove one from the mblk list */ ASSERT(mp_head != NULL); mp = mp_head; mp_head = mp_head->b_next; mp->b_next = NULL; /* statistics for non-unicast packets */ bp = mp->b_rptr; if ((bp[0] & 1) && (flags & GEM_SEND_CTRL) == 0) { if (bcmp(bp, gem_etherbroadcastaddr.ether_addr_octet, ETHERADDRL) == 0) { bcast++; } else { mcast++; } } /* save misc info */ txflag = tbp->txb_flag; txflag |= (flags & GEM_SEND_CTRL) << GEM_TXFLAG_PRIVATE_SHIFT; txflag |= gem_txbuf_options(dp, mp, (uint8_t *)tbp->txb_buf); tbp->txb_flag = txflag; len_total += gem_setup_txbuf_copy(dp, mp, tbp); } (void) gem_tx_load_descs_oo(dp, head, head + nmblk, load_flags); /* Append the tbp at the tail of the active tx buffer list */ mutex_enter(&dp->xmitlock); if ((--dp->tx_busy) == 0) { /* extend the tail of softq, as new packets have been ready. */ dp->tx_softq_tail = dp->tx_free_head; if (!dp->mac_active && (flags & GEM_SEND_CTRL) == 0) { /* * The device status has changed while we are * preparing tx buf. * As we are the last one that make tx non-busy. * wake up someone who may wait for us. */ cv_broadcast(&dp->tx_drain_cv); } else { ASSERT(dp->tx_softq_tail - dp->tx_softq_head > 0); gem_tx_start_unit(dp); } } dp->stats.obytes += len_total; dp->stats.opackets += nmblk; dp->stats.obcast += bcast; dp->stats.omcast += mcast; done: mutex_exit(&dp->xmitlock); return (mp_head); } /* ========================================================== */ /* * error detection and restart routines */ /* ========================================================== */ int gem_restart_nic(struct gem_dev *dp, uint_t flags) { ASSERT(mutex_owned(&dp->intrlock)); DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); #ifdef GEM_DEBUG_LEVEL #if GEM_DEBUG_LEVEL > 1 gem_dump_txbuf(dp, CE_CONT, "gem_restart_nic"); #endif #endif if (dp->mac_suspended) { /* should we return GEM_FAILURE ? */ return (GEM_FAILURE); } /* * We should avoid calling any routines except xxx_chip_reset * when we are resuming the system. */ if (dp->mac_active) { if (flags & GEM_RESTART_KEEP_BUF) { /* stop rx gracefully */ dp->rxmode &= ~RXMODE_ENABLE; (void) (*dp->gc.gc_set_rx_filter)(dp); } (void) gem_mac_stop(dp, flags); } /* reset the chip. */ if ((*dp->gc.gc_reset_chip)(dp) != GEM_SUCCESS) { cmn_err(CE_WARN, "%s: %s: failed to reset chip", dp->name, __func__); goto err; } if (gem_mac_init(dp) != GEM_SUCCESS) { goto err; } /* setup media mode if the link have been up */ if (dp->mii_state == MII_STATE_LINKUP) { if ((dp->gc.gc_set_media)(dp) != GEM_SUCCESS) { goto err; } } /* setup mac address and enable rx filter */ dp->rxmode |= RXMODE_ENABLE; if ((*dp->gc.gc_set_rx_filter)(dp) != GEM_SUCCESS) { goto err; } /* * XXX - a panic happened because of linkdown. * We must check mii_state here, because the link can be down just * before the restart event happen. If the link is down now, * gem_mac_start() will be called from gem_mii_link_check() when * the link become up later. */ if (dp->mii_state == MII_STATE_LINKUP) { /* restart the nic */ ASSERT(!dp->mac_active); (void) gem_mac_start(dp); } return (GEM_SUCCESS); err: return (GEM_FAILURE); } static void gem_tx_timeout(struct gem_dev *dp) { clock_t now; boolean_t tx_sched; struct txbuf *tbp; mutex_enter(&dp->intrlock); tx_sched = B_FALSE; now = ddi_get_lbolt(); mutex_enter(&dp->xmitlock); if (!dp->mac_active || dp->mii_state != MII_STATE_LINKUP) { mutex_exit(&dp->xmitlock); goto schedule_next; } mutex_exit(&dp->xmitlock); /* reclaim transmitted buffers to check the trasmitter hangs or not. */ if (gem_reclaim_txbuf(dp) != GEM_SUCCESS) { /* tx error happened, reset transmitter in the chip */ (void) gem_restart_nic(dp, 0); tx_sched = B_TRUE; dp->tx_blocked = (clock_t)0; goto schedule_next; } mutex_enter(&dp->xmitlock); /* check if the transmitter thread is stuck */ if (dp->tx_active_head == dp->tx_active_tail) { /* no tx buffer is loaded to the nic */ if (dp->tx_blocked && now - dp->tx_blocked > dp->gc.gc_tx_timeout_interval) { gem_dump_txbuf(dp, CE_WARN, "gem_tx_timeout: tx blocked"); tx_sched = B_TRUE; dp->tx_blocked = (clock_t)0; } mutex_exit(&dp->xmitlock); goto schedule_next; } tbp = GET_TXBUF(dp, dp->tx_active_head); if (now - tbp->txb_stime < dp->gc.gc_tx_timeout) { mutex_exit(&dp->xmitlock); goto schedule_next; } mutex_exit(&dp->xmitlock); gem_dump_txbuf(dp, CE_WARN, "gem_tx_timeout: tx timeout"); /* discard untransmitted packet and restart tx. */ (void) gem_restart_nic(dp, GEM_RESTART_NOWAIT); tx_sched = B_TRUE; dp->tx_blocked = (clock_t)0; schedule_next: mutex_exit(&dp->intrlock); /* restart the downstream if needed */ if (tx_sched) { mac_tx_update(dp->mh); } DPRINTF(4, (CE_CONT, "!%s: blocked:%d active_head:%d active_tail:%d desc_intr:%d", dp->name, BOOLEAN(dp->tx_blocked), dp->tx_active_head, dp->tx_active_tail, dp->tx_desc_intr)); dp->timeout_id = timeout((void (*)(void *))gem_tx_timeout, (void *)dp, dp->gc.gc_tx_timeout_interval); } /* ================================================================== */ /* * Interrupt handler */ /* ================================================================== */ __INLINE__ static void gem_append_rxbuf(struct gem_dev *dp, struct rxbuf *rbp_head) { struct rxbuf *rbp; seqnum_t tail; int rx_ring_size = dp->gc.gc_rx_ring_size; ASSERT(rbp_head != NULL); ASSERT(mutex_owned(&dp->intrlock)); DPRINTF(3, (CE_CONT, "!%s: %s: slot_head:%d, slot_tail:%d", dp->name, __func__, dp->rx_active_head, dp->rx_active_tail)); /* * Add new buffers into active rx buffer list */ if (dp->rx_buf_head == NULL) { dp->rx_buf_head = rbp_head; ASSERT(dp->rx_buf_tail == NULL); } else { dp->rx_buf_tail->rxb_next = rbp_head; } tail = dp->rx_active_tail; for (rbp = rbp_head; rbp; rbp = rbp->rxb_next) { /* need to notify the tail for the lower layer */ dp->rx_buf_tail = rbp; dp->gc.gc_rx_desc_write(dp, SLOT(tail, rx_ring_size), rbp->rxb_dmacookie, rbp->rxb_nfrags); dp->rx_active_tail = tail = tail + 1; } } #pragma inline(gem_append_rxbuf) mblk_t * gem_get_packet_default(struct gem_dev *dp, struct rxbuf *rbp, size_t len) { int rx_header_len = dp->gc.gc_rx_header_len; uint8_t *bp; mblk_t *mp; /* allocate a new mblk */ if (mp = allocb(len + VTAG_SIZE, BPRI_MED)) { ASSERT(mp->b_next == NULL); ASSERT(mp->b_cont == NULL); mp->b_rptr += VTAG_SIZE; bp = mp->b_rptr; mp->b_wptr = bp + len; /* * flush the range of the entire buffer to invalidate * all of corresponding dirty entries in iocache. */ (void) ddi_dma_sync(rbp->rxb_dh, rx_header_len, 0, DDI_DMA_SYNC_FORKERNEL); bcopy(rbp->rxb_buf + rx_header_len, bp, len); } return (mp); } #ifdef GEM_DEBUG_LEVEL uint_t gem_rx_pkts[17]; #endif int gem_receive(struct gem_dev *dp) { uint64_t len_total = 0; struct rxbuf *rbp; mblk_t *mp; int cnt = 0; uint64_t rxstat; struct rxbuf *newbufs; struct rxbuf **newbufs_tailp; mblk_t *rx_head; mblk_t **rx_tailp; int rx_ring_size = dp->gc.gc_rx_ring_size; seqnum_t active_head; uint64_t (*rx_desc_stat)(struct gem_dev *dp, int slot, int ndesc); int ethermin = ETHERMIN; int ethermax = dp->mtu + sizeof (struct ether_header); int rx_header_len = dp->gc.gc_rx_header_len; ASSERT(mutex_owned(&dp->intrlock)); DPRINTF(3, (CE_CONT, "!%s: gem_receive: rx_buf_head:%p", dp->name, dp->rx_buf_head)); rx_desc_stat = dp->gc.gc_rx_desc_stat; newbufs_tailp = &newbufs; rx_tailp = &rx_head; for (active_head = dp->rx_active_head; (rbp = dp->rx_buf_head) != NULL; active_head++) { int len; if (cnt == 0) { cnt = max(dp->poll_pkt_delay*2, 10); cnt = min(cnt, dp->rx_active_tail - active_head); gem_rx_desc_dma_sync(dp, SLOT(active_head, rx_ring_size), cnt, DDI_DMA_SYNC_FORKERNEL); } if (rx_header_len > 0) { (void) ddi_dma_sync(rbp->rxb_dh, 0, rx_header_len, DDI_DMA_SYNC_FORKERNEL); } if (((rxstat = (*rx_desc_stat)(dp, SLOT(active_head, rx_ring_size), rbp->rxb_nfrags)) & (GEM_RX_DONE | GEM_RX_ERR)) == 0) { /* not received yet */ break; } /* Remove the head of the rx buffer list */ dp->rx_buf_head = rbp->rxb_next; cnt--; if (rxstat & GEM_RX_ERR) { goto next; } len = rxstat & GEM_RX_LEN; DPRINTF(3, (CE_CONT, "!%s: %s: rxstat:0x%llx, len:0x%x", dp->name, __func__, rxstat, len)); /* * Copy the packet */ if ((mp = dp->gc.gc_get_packet(dp, rbp, len)) == NULL) { /* no memory, discard the packet */ dp->stats.norcvbuf++; goto next; } /* * Process VLAN tag */ ethermin = ETHERMIN; ethermax = dp->mtu + sizeof (struct ether_header); if (GET_NET16(mp->b_rptr + VTAG_OFF) == VTAG_TPID) { ethermax += VTAG_SIZE; } /* check packet size */ if (len < ethermin) { dp->stats.errrcv++; dp->stats.runt++; freemsg(mp); goto next; } if (len > ethermax) { dp->stats.errrcv++; dp->stats.frame_too_long++; freemsg(mp); goto next; } len_total += len; #ifdef GEM_DEBUG_VLAN if (GET_ETHERTYPE(mp->b_rptr) == VTAG_TPID) { gem_dump_packet(dp, (char *)__func__, mp, B_TRUE); } #endif /* append received packet to temporaly rx buffer list */ *rx_tailp = mp; rx_tailp = &mp->b_next; if (mp->b_rptr[0] & 1) { if (bcmp(mp->b_rptr, gem_etherbroadcastaddr.ether_addr_octet, ETHERADDRL) == 0) { dp->stats.rbcast++; } else { dp->stats.rmcast++; } } next: ASSERT(rbp != NULL); /* append new one to temporal new buffer list */ *newbufs_tailp = rbp; newbufs_tailp = &rbp->rxb_next; } /* advance rx_active_head */ if ((cnt = active_head - dp->rx_active_head) > 0) { dp->stats.rbytes += len_total; dp->stats.rpackets += cnt; } dp->rx_active_head = active_head; /* terminate the working list */ *newbufs_tailp = NULL; *rx_tailp = NULL; if (dp->rx_buf_head == NULL) { dp->rx_buf_tail = NULL; } DPRINTF(4, (CE_CONT, "%s: %s: cnt:%d, rx_head:%p", dp->name, __func__, cnt, rx_head)); if (newbufs) { /* * fillfull rx list with new buffers */ seqnum_t head; /* save current tail */ head = dp->rx_active_tail; gem_append_rxbuf(dp, newbufs); /* call hw depend start routine if we have. */ dp->gc.gc_rx_start(dp, SLOT(head, rx_ring_size), dp->rx_active_tail - head); } if (rx_head) { /* * send up received packets */ mutex_exit(&dp->intrlock); mac_rx(dp->mh, dp->mac_rx_ring_ha, rx_head); mutex_enter(&dp->intrlock); } #ifdef GEM_DEBUG_LEVEL gem_rx_pkts[min(cnt, sizeof (gem_rx_pkts)/sizeof (uint_t)-1)]++; #endif return (cnt); } boolean_t gem_tx_done(struct gem_dev *dp) { boolean_t tx_sched = B_FALSE; if (gem_reclaim_txbuf(dp) != GEM_SUCCESS) { (void) gem_restart_nic(dp, GEM_RESTART_KEEP_BUF); DPRINTF(2, (CE_CONT, "!%s: gem_tx_done: tx_desc: %d %d", dp->name, dp->tx_active_head, dp->tx_active_tail)); tx_sched = B_TRUE; goto x; } mutex_enter(&dp->xmitlock); /* XXX - we must not have any packets in soft queue */ ASSERT(dp->tx_softq_head == dp->tx_softq_tail); /* * If we won't have chance to get more free tx buffers, and blocked, * it is worth to reschedule the downstream i.e. tx side. */ ASSERT(dp->tx_desc_intr - dp->tx_desc_head >= 0); if (dp->tx_blocked && dp->tx_desc_intr == dp->tx_desc_head) { /* * As no further tx-done interrupts are scheduled, this * is the last chance to kick tx side, which may be * blocked now, otherwise the tx side never works again. */ tx_sched = B_TRUE; dp->tx_blocked = (clock_t)0; dp->tx_max_packets = min(dp->tx_max_packets + 2, dp->gc.gc_tx_buf_limit); } mutex_exit(&dp->xmitlock); DPRINTF(3, (CE_CONT, "!%s: %s: ret: blocked:%d", dp->name, __func__, BOOLEAN(dp->tx_blocked))); x: return (tx_sched); } static uint_t gem_intr(struct gem_dev *dp) { uint_t ret; mutex_enter(&dp->intrlock); if (dp->mac_suspended) { mutex_exit(&dp->intrlock); return (DDI_INTR_UNCLAIMED); } dp->intr_busy = B_TRUE; ret = (*dp->gc.gc_interrupt)(dp); if (ret == DDI_INTR_UNCLAIMED) { dp->intr_busy = B_FALSE; mutex_exit(&dp->intrlock); return (ret); } if (!dp->mac_active) { cv_broadcast(&dp->tx_drain_cv); } dp->stats.intr++; dp->intr_busy = B_FALSE; mutex_exit(&dp->intrlock); if (ret & INTR_RESTART_TX) { DPRINTF(4, (CE_CONT, "!%s: calling mac_tx_update", dp->name)); mac_tx_update(dp->mh); ret &= ~INTR_RESTART_TX; } return (ret); } static void gem_intr_watcher(struct gem_dev *dp) { (void) gem_intr(dp); /* schedule next call of tu_intr_watcher */ dp->intr_watcher_id = timeout((void (*)(void *))gem_intr_watcher, (void *)dp, 1); } /* ======================================================================== */ /* * MII support routines */ /* ======================================================================== */ static void gem_choose_forcedmode(struct gem_dev *dp) { /* choose media mode */ if (dp->anadv_1000fdx || dp->anadv_1000hdx) { dp->speed = GEM_SPD_1000; dp->full_duplex = dp->anadv_1000fdx; } else if (dp->anadv_100fdx || dp->anadv_100t4) { dp->speed = GEM_SPD_100; dp->full_duplex = B_TRUE; } else if (dp->anadv_100hdx) { dp->speed = GEM_SPD_100; dp->full_duplex = B_FALSE; } else { dp->speed = GEM_SPD_10; dp->full_duplex = dp->anadv_10fdx; } } uint16_t gem_mii_read(struct gem_dev *dp, uint_t reg) { if ((dp->mii_status & MII_STATUS_MFPRMBLSUPR) == 0) { (*dp->gc.gc_mii_sync)(dp); } return ((*dp->gc.gc_mii_read)(dp, reg)); } void gem_mii_write(struct gem_dev *dp, uint_t reg, uint16_t val) { if ((dp->mii_status & MII_STATUS_MFPRMBLSUPR) == 0) { (*dp->gc.gc_mii_sync)(dp); } (*dp->gc.gc_mii_write)(dp, reg, val); } #define fc_cap_decode(x) \ ((((x) & MII_ABILITY_PAUSE) ? 1 : 0) | \ (((x) & MII_ABILITY_ASM_DIR) ? 2 : 0)) int gem_mii_config_default(struct gem_dev *dp) { uint16_t mii_stat; uint16_t val; static uint16_t fc_cap_encode[4] = { /* none */ 0, /* symmetric */ MII_ABILITY_PAUSE, /* tx */ MII_ABILITY_ASM_DIR, /* rx-symmetric */ MII_ABILITY_PAUSE | MII_ABILITY_ASM_DIR, }; DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); /* * Configure bits in advertisement register */ mii_stat = dp->mii_status; DPRINTF(1, (CE_CONT, "!%s: %s: MII_STATUS reg:%b", dp->name, __func__, mii_stat, MII_STATUS_BITS)); if ((mii_stat & MII_STATUS_ABILITY_TECH) == 0) { /* it's funny */ cmn_err(CE_WARN, "!%s: wrong ability bits: mii_status:%b", dp->name, mii_stat, MII_STATUS_BITS); return (GEM_FAILURE); } /* Do not change the rest of the ability bits in the advert reg */ val = gem_mii_read(dp, MII_AN_ADVERT) & ~MII_ABILITY_ALL; DPRINTF(0, (CE_CONT, "!%s: %s: 100T4:%d 100F:%d 100H:%d 10F:%d 10H:%d", dp->name, __func__, dp->anadv_100t4, dp->anadv_100fdx, dp->anadv_100hdx, dp->anadv_10fdx, dp->anadv_10hdx)); if (dp->anadv_100t4) { val |= MII_ABILITY_100BASE_T4; } if (dp->anadv_100fdx) { val |= MII_ABILITY_100BASE_TX_FD; } if (dp->anadv_100hdx) { val |= MII_ABILITY_100BASE_TX; } if (dp->anadv_10fdx) { val |= MII_ABILITY_10BASE_T_FD; } if (dp->anadv_10hdx) { val |= MII_ABILITY_10BASE_T; } /* set flow control capability */ val |= fc_cap_encode[dp->anadv_flow_control]; DPRINTF(0, (CE_CONT, "!%s: %s: setting MII_AN_ADVERT reg:%b, mii_mode:%d, fc:%d", dp->name, __func__, val, MII_ABILITY_BITS, dp->gc.gc_mii_mode, dp->anadv_flow_control)); gem_mii_write(dp, MII_AN_ADVERT, val); if (mii_stat & MII_STATUS_XSTATUS) { /* * 1000Base-T GMII support */ if (!dp->anadv_autoneg) { /* enable manual configuration */ val = MII_1000TC_CFG_EN; } else { val = 0; if (dp->anadv_1000fdx) { val |= MII_1000TC_ADV_FULL; } if (dp->anadv_1000hdx) { val |= MII_1000TC_ADV_HALF; } } DPRINTF(0, (CE_CONT, "!%s: %s: setting MII_1000TC reg:%b", dp->name, __func__, val, MII_1000TC_BITS)); gem_mii_write(dp, MII_1000TC, val); } return (GEM_SUCCESS); } #define GEM_LINKUP(dp) mac_link_update((dp)->mh, LINK_STATE_UP) #define GEM_LINKDOWN(dp) mac_link_update((dp)->mh, LINK_STATE_DOWN) static uint8_t gem_fc_result[4 /* my cap */ ][4 /* lp cap */] = { /* none symm tx rx/symm */ /* none */ {FLOW_CONTROL_NONE, FLOW_CONTROL_NONE, FLOW_CONTROL_NONE, FLOW_CONTROL_NONE}, /* sym */ {FLOW_CONTROL_NONE, FLOW_CONTROL_SYMMETRIC, FLOW_CONTROL_NONE, FLOW_CONTROL_SYMMETRIC}, /* tx */ {FLOW_CONTROL_NONE, FLOW_CONTROL_NONE, FLOW_CONTROL_NONE, FLOW_CONTROL_TX_PAUSE}, /* rx/symm */ {FLOW_CONTROL_NONE, FLOW_CONTROL_SYMMETRIC, FLOW_CONTROL_RX_PAUSE, FLOW_CONTROL_SYMMETRIC}, }; static char *gem_fc_type[] = { "without", "with symmetric", "with tx", "with rx", }; boolean_t gem_mii_link_check(struct gem_dev *dp) { uint16_t old_mii_state; boolean_t tx_sched = B_FALSE; uint16_t status; uint16_t advert; uint16_t lpable; uint16_t exp; uint16_t ctl1000; uint16_t stat1000; uint16_t val; clock_t now; clock_t diff; int linkdown_action; boolean_t fix_phy = B_FALSE; now = ddi_get_lbolt(); old_mii_state = dp->mii_state; DPRINTF(3, (CE_CONT, "!%s: %s: time:%d state:%d", dp->name, __func__, now, dp->mii_state)); diff = now - dp->mii_last_check; dp->mii_last_check = now; /* * For NWAM, don't show linkdown state right * after the system boots */ if (dp->linkup_delay > 0) { if (dp->linkup_delay > diff) { dp->linkup_delay -= diff; } else { /* link up timeout */ dp->linkup_delay = -1; } } next_nowait: switch (dp->mii_state) { case MII_STATE_UNKNOWN: /* power-up, DP83840 requires 32 sync bits */ (*dp->gc.gc_mii_sync)(dp); goto reset_phy; case MII_STATE_RESETTING: dp->mii_timer -= diff; if (dp->mii_timer > 0) { /* don't read phy registers in resetting */ dp->mii_interval = WATCH_INTERVAL_FAST; goto next; } /* Timer expired, ensure reset bit is not set */ if (dp->mii_status & MII_STATUS_MFPRMBLSUPR) { /* some phys need sync bits after reset */ (*dp->gc.gc_mii_sync)(dp); } val = gem_mii_read(dp, MII_CONTROL); if (val & MII_CONTROL_RESET) { cmn_err(CE_NOTE, "!%s: time:%ld resetting phy not complete." " mii_control:0x%b", dp->name, ddi_get_lbolt(), val, MII_CONTROL_BITS); } /* ensure neither isolated nor pwrdown nor auto-nego mode */ /* XXX -- this operation is required for NS DP83840A. */ gem_mii_write(dp, MII_CONTROL, 0); /* As resetting PHY has completed, configure PHY registers */ if ((*dp->gc.gc_mii_config)(dp) != GEM_SUCCESS) { /* we failed to configure PHY. */ goto reset_phy; } /* mii_config may disable autonegatiation */ gem_choose_forcedmode(dp); dp->mii_lpable = 0; dp->mii_advert = 0; dp->mii_exp = 0; dp->mii_ctl1000 = 0; dp->mii_stat1000 = 0; dp->flow_control = FLOW_CONTROL_NONE; if (!dp->anadv_autoneg) { /* skip auto-negotiation phase */ dp->mii_state = MII_STATE_MEDIA_SETUP; dp->mii_timer = 0; dp->mii_interval = 0; goto next_nowait; } /* Issue auto-negotiation command */ goto autonego; case MII_STATE_AUTONEGOTIATING: /* * Autonegotiation is in progress */ dp->mii_timer -= diff; if (dp->mii_timer - (dp->gc.gc_mii_an_timeout - dp->gc.gc_mii_an_wait) > 0) { /* * wait for a while, typically autonegotiation * completes in 2.3 - 2.5 sec. */ dp->mii_interval = WATCH_INTERVAL_FAST; goto next; } /* read PHY status */ status = gem_mii_read(dp, MII_STATUS); DPRINTF(4, (CE_CONT, "!%s: %s: called: mii_state:%d MII_STATUS reg:%b", dp->name, __func__, dp->mii_state, status, MII_STATUS_BITS)); if (status & MII_STATUS_REMFAULT) { /* * The link parnert told me something wrong happend. * What do we do ? */ cmn_err(CE_CONT, "!%s: auto-negotiation failed: remote fault", dp->name); goto autonego; } if ((status & MII_STATUS_ANDONE) == 0) { if (dp->mii_timer <= 0) { /* * Auto-negotiation was timed out, * try again w/o resetting phy. */ if (!dp->mii_supress_msg) { cmn_err(CE_WARN, "!%s: auto-negotiation failed: timeout", dp->name); dp->mii_supress_msg = B_TRUE; } goto autonego; } /* * Auto-negotiation is in progress. Wait. */ dp->mii_interval = dp->gc.gc_mii_an_watch_interval; goto next; } /* * Auto-negotiation have completed. * Assume linkdown and fall through. */ dp->mii_supress_msg = B_FALSE; dp->mii_state = MII_STATE_AN_DONE; DPRINTF(0, (CE_CONT, "!%s: auto-negotiation completed, MII_STATUS:%b", dp->name, status, MII_STATUS_BITS)); if (dp->gc.gc_mii_an_delay > 0) { dp->mii_timer = dp->gc.gc_mii_an_delay; dp->mii_interval = drv_usectohz(20*1000); goto next; } dp->mii_timer = 0; diff = 0; goto next_nowait; case MII_STATE_AN_DONE: /* * Auto-negotiation have done. Now we can set up media. */ dp->mii_timer -= diff; if (dp->mii_timer > 0) { /* wait for a while */ dp->mii_interval = WATCH_INTERVAL_FAST; goto next; } /* * set up the result of auto negotiation */ /* * Read registers required to determin current * duplex mode and media speed. */ if (dp->gc.gc_mii_an_delay > 0) { /* * As the link watcher context has been suspended, * 'status' is invalid. We must status register here */ status = gem_mii_read(dp, MII_STATUS); } advert = gem_mii_read(dp, MII_AN_ADVERT); lpable = gem_mii_read(dp, MII_AN_LPABLE); exp = gem_mii_read(dp, MII_AN_EXPANSION); if (exp == 0xffff) { /* some phys don't have exp register */ exp = 0; } ctl1000 = 0; stat1000 = 0; if (dp->mii_status & MII_STATUS_XSTATUS) { ctl1000 = gem_mii_read(dp, MII_1000TC); stat1000 = gem_mii_read(dp, MII_1000TS); } dp->mii_lpable = lpable; dp->mii_advert = advert; dp->mii_exp = exp; dp->mii_ctl1000 = ctl1000; dp->mii_stat1000 = stat1000; cmn_err(CE_CONT, "!%s: auto-negotiation done, advert:%b, lpable:%b, exp:%b", dp->name, advert, MII_ABILITY_BITS, lpable, MII_ABILITY_BITS, exp, MII_AN_EXP_BITS); if (dp->mii_status & MII_STATUS_XSTATUS) { cmn_err(CE_CONT, "! MII_1000TC:%b, MII_1000TS:%b", ctl1000, MII_1000TC_BITS, stat1000, MII_1000TS_BITS); } if (gem_population(lpable) <= 1 && (exp & MII_AN_EXP_LPCANAN) == 0) { if ((advert & MII_ABILITY_TECH) != lpable) { cmn_err(CE_WARN, "!%s: but the link partnar doesn't seem" " to have auto-negotiation capability." " please check the link configuration.", dp->name); } /* * it should be result of pararell detection, which * cannot detect duplex mode. */ if (lpable & MII_ABILITY_100BASE_TX) { /* * we prefer full duplex mode for 100Mbps * connection, if we can. */ lpable |= advert & MII_ABILITY_100BASE_TX_FD; } if ((advert & lpable) == 0 && lpable & MII_ABILITY_10BASE_T) { lpable |= advert & MII_ABILITY_10BASE_T_FD; } /* * as the link partnar isn't auto-negotiatable, use * fixed mode temporally. */ fix_phy = B_TRUE; } else if (lpable == 0) { cmn_err(CE_WARN, "!%s: wrong lpable.", dp->name); goto reset_phy; } /* * configure current link mode according to AN priority. */ val = advert & lpable; if ((ctl1000 & MII_1000TC_ADV_FULL) && (stat1000 & MII_1000TS_LP_FULL)) { /* 1000BaseT & full duplex */ dp->speed = GEM_SPD_1000; dp->full_duplex = B_TRUE; } else if ((ctl1000 & MII_1000TC_ADV_HALF) && (stat1000 & MII_1000TS_LP_HALF)) { /* 1000BaseT & half duplex */ dp->speed = GEM_SPD_1000; dp->full_duplex = B_FALSE; } else if (val & MII_ABILITY_100BASE_TX_FD) { /* 100BaseTx & full duplex */ dp->speed = GEM_SPD_100; dp->full_duplex = B_TRUE; } else if (val & MII_ABILITY_100BASE_T4) { /* 100BaseT4 & full duplex */ dp->speed = GEM_SPD_100; dp->full_duplex = B_TRUE; } else if (val & MII_ABILITY_100BASE_TX) { /* 100BaseTx & half duplex */ dp->speed = GEM_SPD_100; dp->full_duplex = B_FALSE; } else if (val & MII_ABILITY_10BASE_T_FD) { /* 10BaseT & full duplex */ dp->speed = GEM_SPD_10; dp->full_duplex = B_TRUE; } else if (val & MII_ABILITY_10BASE_T) { /* 10BaseT & half duplex */ dp->speed = GEM_SPD_10; dp->full_duplex = B_FALSE; } else { /* * It seems that the link partnar doesn't have * auto-negotiation capability and our PHY * could not report the correct current mode. * We guess current mode by mii_control register. */ val = gem_mii_read(dp, MII_CONTROL); /* select 100m full or 10m half */ dp->speed = (val & MII_CONTROL_100MB) ? GEM_SPD_100 : GEM_SPD_10; dp->full_duplex = dp->speed != GEM_SPD_10; fix_phy = B_TRUE; cmn_err(CE_NOTE, "!%s: auto-negotiation done but " "common ability not found.\n" "PHY state: control:%b advert:%b lpable:%b\n" "guessing %d Mbps %s duplex mode", dp->name, val, MII_CONTROL_BITS, advert, MII_ABILITY_BITS, lpable, MII_ABILITY_BITS, gem_speed_value[dp->speed], dp->full_duplex ? "full" : "half"); } if (dp->full_duplex) { dp->flow_control = gem_fc_result[fc_cap_decode(advert)] [fc_cap_decode(lpable)]; } else { dp->flow_control = FLOW_CONTROL_NONE; } dp->mii_state = MII_STATE_MEDIA_SETUP; /* FALLTHROUGH */ case MII_STATE_MEDIA_SETUP: dp->mii_state = MII_STATE_LINKDOWN; dp->mii_timer = dp->gc.gc_mii_linkdown_timeout; DPRINTF(2, (CE_CONT, "!%s: setup midia mode done", dp->name)); dp->mii_supress_msg = B_FALSE; /* use short interval */ dp->mii_interval = WATCH_INTERVAL_FAST; if ((!dp->anadv_autoneg) || dp->gc.gc_mii_an_oneshot || fix_phy) { /* * write specified mode to phy. */ val = gem_mii_read(dp, MII_CONTROL); val &= ~(MII_CONTROL_SPEED | MII_CONTROL_FDUPLEX | MII_CONTROL_ANE | MII_CONTROL_RSAN); if (dp->full_duplex) { val |= MII_CONTROL_FDUPLEX; } switch (dp->speed) { case GEM_SPD_1000: val |= MII_CONTROL_1000MB; break; case GEM_SPD_100: val |= MII_CONTROL_100MB; break; default: cmn_err(CE_WARN, "%s: unknown speed:%d", dp->name, dp->speed); /* FALLTHROUGH */ case GEM_SPD_10: /* for GEM_SPD_10, do nothing */ break; } if (dp->mii_status & MII_STATUS_XSTATUS) { gem_mii_write(dp, MII_1000TC, MII_1000TC_CFG_EN); } gem_mii_write(dp, MII_CONTROL, val); } if (dp->nic_state >= NIC_STATE_INITIALIZED) { /* notify the result of auto-negotiation to mac */ (*dp->gc.gc_set_media)(dp); } if ((void *)dp->gc.gc_mii_tune_phy) { /* for built-in sis900 */ /* XXX - this code should be removed. */ (*dp->gc.gc_mii_tune_phy)(dp); } goto next_nowait; case MII_STATE_LINKDOWN: status = gem_mii_read(dp, MII_STATUS); if (status & MII_STATUS_LINKUP) { /* * Link going up */ dp->mii_state = MII_STATE_LINKUP; dp->mii_supress_msg = B_FALSE; DPRINTF(0, (CE_CONT, "!%s: link up detected: mii_stat:%b", dp->name, status, MII_STATUS_BITS)); /* * MII_CONTROL_100MB and MII_CONTROL_FDUPLEX are * ignored when MII_CONTROL_ANE is set. */ cmn_err(CE_CONT, "!%s: Link up: %d Mbps %s duplex %s flow control", dp->name, gem_speed_value[dp->speed], dp->full_duplex ? "full" : "half", gem_fc_type[dp->flow_control]); dp->mii_interval = dp->gc.gc_mii_link_watch_interval; /* XXX - we need other timer to watch statictics */ if (dp->gc.gc_mii_hw_link_detection && dp->nic_state == NIC_STATE_ONLINE) { dp->mii_interval = 0; } if (dp->nic_state == NIC_STATE_ONLINE) { if (!dp->mac_active) { (void) gem_mac_start(dp); } tx_sched = B_TRUE; } goto next; } dp->mii_supress_msg = B_TRUE; if (dp->anadv_autoneg) { dp->mii_timer -= diff; if (dp->mii_timer <= 0) { /* * link down timer expired. * need to restart auto-negotiation. */ linkdown_action = dp->gc.gc_mii_linkdown_timeout_action; goto restart_autonego; } } /* don't change mii_state */ break; case MII_STATE_LINKUP: status = gem_mii_read(dp, MII_STATUS); if ((status & MII_STATUS_LINKUP) == 0) { /* * Link going down */ cmn_err(CE_NOTE, "!%s: link down detected: mii_stat:%b", dp->name, status, MII_STATUS_BITS); if (dp->nic_state == NIC_STATE_ONLINE && dp->mac_active && dp->gc.gc_mii_stop_mac_on_linkdown) { (void) gem_mac_stop(dp, 0); if (dp->tx_blocked) { /* drain tx */ tx_sched = B_TRUE; } } if (dp->anadv_autoneg) { /* need to restart auto-negotiation */ linkdown_action = dp->gc.gc_mii_linkdown_action; goto restart_autonego; } dp->mii_state = MII_STATE_LINKDOWN; dp->mii_timer = dp->gc.gc_mii_linkdown_timeout; if ((void *)dp->gc.gc_mii_tune_phy) { /* for built-in sis900 */ (*dp->gc.gc_mii_tune_phy)(dp); } dp->mii_interval = dp->gc.gc_mii_link_watch_interval; goto next; } /* don't change mii_state */ if (dp->gc.gc_mii_hw_link_detection && dp->nic_state == NIC_STATE_ONLINE) { dp->mii_interval = 0; goto next; } break; } dp->mii_interval = dp->gc.gc_mii_link_watch_interval; goto next; /* Actions on the end of state routine */ restart_autonego: switch (linkdown_action) { case MII_ACTION_RESET: if (!dp->mii_supress_msg) { cmn_err(CE_CONT, "!%s: resetting PHY", dp->name); } dp->mii_supress_msg = B_TRUE; goto reset_phy; case MII_ACTION_NONE: dp->mii_supress_msg = B_TRUE; if (dp->gc.gc_mii_an_oneshot) { goto autonego; } /* PHY will restart autonego automatically */ dp->mii_state = MII_STATE_AUTONEGOTIATING; dp->mii_timer = dp->gc.gc_mii_an_timeout; dp->mii_interval = dp->gc.gc_mii_an_watch_interval; goto next; case MII_ACTION_RSA: if (!dp->mii_supress_msg) { cmn_err(CE_CONT, "!%s: restarting auto-negotiation", dp->name); } dp->mii_supress_msg = B_TRUE; goto autonego; default: cmn_err(CE_WARN, "!%s: unknowm linkdown action: %d", dp->name, dp->gc.gc_mii_linkdown_action); dp->mii_supress_msg = B_TRUE; } /* NOTREACHED */ reset_phy: if (!dp->mii_supress_msg) { cmn_err(CE_CONT, "!%s: resetting PHY", dp->name); } dp->mii_state = MII_STATE_RESETTING; dp->mii_timer = dp->gc.gc_mii_reset_timeout; if (!dp->gc.gc_mii_dont_reset) { gem_mii_write(dp, MII_CONTROL, MII_CONTROL_RESET); } dp->mii_interval = WATCH_INTERVAL_FAST; goto next; autonego: if (!dp->mii_supress_msg) { cmn_err(CE_CONT, "!%s: auto-negotiation started", dp->name); } dp->mii_state = MII_STATE_AUTONEGOTIATING; dp->mii_timer = dp->gc.gc_mii_an_timeout; /* start/restart auto nego */ val = gem_mii_read(dp, MII_CONTROL) & ~(MII_CONTROL_ISOLATE | MII_CONTROL_PWRDN | MII_CONTROL_RESET); gem_mii_write(dp, MII_CONTROL, val | MII_CONTROL_RSAN | MII_CONTROL_ANE); dp->mii_interval = dp->gc.gc_mii_an_watch_interval; next: if (dp->link_watcher_id == 0 && dp->mii_interval) { /* we must schedule next mii_watcher */ dp->link_watcher_id = timeout((void (*)(void *))&gem_mii_link_watcher, (void *)dp, dp->mii_interval); } if (old_mii_state != dp->mii_state) { /* notify new mii link state */ if (dp->mii_state == MII_STATE_LINKUP) { dp->linkup_delay = 0; GEM_LINKUP(dp); } else if (dp->linkup_delay <= 0) { GEM_LINKDOWN(dp); } } else if (dp->linkup_delay < 0) { /* first linkup timeout */ dp->linkup_delay = 0; GEM_LINKDOWN(dp); } return (tx_sched); } static void gem_mii_link_watcher(struct gem_dev *dp) { boolean_t tx_sched; mutex_enter(&dp->intrlock); dp->link_watcher_id = 0; tx_sched = gem_mii_link_check(dp); #if GEM_DEBUG_LEVEL > 2 if (dp->link_watcher_id == 0) { cmn_err(CE_CONT, "%s: link watcher stopped", dp->name); } #endif mutex_exit(&dp->intrlock); if (tx_sched) { /* kick potentially stopped downstream */ mac_tx_update(dp->mh); } } int gem_mii_probe_default(struct gem_dev *dp) { int8_t phy; uint16_t status; uint16_t adv; uint16_t adv_org; DPRINTF(3, (CE_CONT, "!%s: %s: called", dp->name, __func__)); /* * Scan PHY */ /* ensure to send sync bits */ dp->mii_status = 0; /* Try default phy first */ if (dp->mii_phy_addr) { status = gem_mii_read(dp, MII_STATUS); if (status != 0xffff && status != 0) { gem_mii_write(dp, MII_CONTROL, 0); goto PHY_found; } if (dp->mii_phy_addr < 0) { cmn_err(CE_NOTE, "!%s: failed to probe default internal and/or non-MII PHY", dp->name); return (GEM_FAILURE); } cmn_err(CE_NOTE, "!%s: failed to probe default MII PHY at %d", dp->name, dp->mii_phy_addr); } /* Try all possible address */ for (phy = dp->gc.gc_mii_addr_min; phy < 32; phy++) { dp->mii_phy_addr = phy; status = gem_mii_read(dp, MII_STATUS); if (status != 0xffff && status != 0) { gem_mii_write(dp, MII_CONTROL, 0); goto PHY_found; } } for (phy = dp->gc.gc_mii_addr_min; phy < 32; phy++) { dp->mii_phy_addr = phy; gem_mii_write(dp, MII_CONTROL, 0); status = gem_mii_read(dp, MII_STATUS); if (status != 0xffff && status != 0) { goto PHY_found; } } cmn_err(CE_NOTE, "!%s: no MII PHY found", dp->name); dp->mii_phy_addr = -1; return (GEM_FAILURE); PHY_found: dp->mii_status = status; dp->mii_phy_id = (gem_mii_read(dp, MII_PHYIDH) << 16) | gem_mii_read(dp, MII_PHYIDL); if (dp->mii_phy_addr < 0) { cmn_err(CE_CONT, "!%s: using internal/non-MII PHY(0x%08x)", dp->name, dp->mii_phy_id); } else { cmn_err(CE_CONT, "!%s: MII PHY (0x%08x) found at %d", dp->name, dp->mii_phy_id, dp->mii_phy_addr); } cmn_err(CE_CONT, "!%s: PHY control:%b, status:%b, advert:%b, lpar:%b", dp->name, gem_mii_read(dp, MII_CONTROL), MII_CONTROL_BITS, status, MII_STATUS_BITS, gem_mii_read(dp, MII_AN_ADVERT), MII_ABILITY_BITS, gem_mii_read(dp, MII_AN_LPABLE), MII_ABILITY_BITS); dp->mii_xstatus = 0; if (status & MII_STATUS_XSTATUS) { dp->mii_xstatus = gem_mii_read(dp, MII_XSTATUS); cmn_err(CE_CONT, "!%s: xstatus:%b", dp->name, dp->mii_xstatus, MII_XSTATUS_BITS); } /* check if the phy can advertize pause abilities */ adv_org = gem_mii_read(dp, MII_AN_ADVERT); gem_mii_write(dp, MII_AN_ADVERT, MII_ABILITY_PAUSE | MII_ABILITY_ASM_DIR); adv = gem_mii_read(dp, MII_AN_ADVERT); if ((adv & MII_ABILITY_PAUSE) == 0) { dp->gc.gc_flow_control &= ~1; } if ((adv & MII_ABILITY_ASM_DIR) == 0) { dp->gc.gc_flow_control &= ~2; } gem_mii_write(dp, MII_AN_ADVERT, adv_org); return (GEM_SUCCESS); } static void gem_mii_start(struct gem_dev *dp) { DPRINTF(3, (CE_CONT, "!%s: %s: called", dp->name, __func__)); /* make a first call of check link */ dp->mii_state = MII_STATE_UNKNOWN; dp->mii_last_check = ddi_get_lbolt(); dp->linkup_delay = dp->gc.gc_mii_linkdown_timeout; (void) gem_mii_link_watcher(dp); } static void gem_mii_stop(struct gem_dev *dp) { DPRINTF(3, (CE_CONT, "!%s: %s: called", dp->name, __func__)); /* Ensure timer routine stopped */ mutex_enter(&dp->intrlock); if (dp->link_watcher_id) { while (untimeout(dp->link_watcher_id) == -1) ; dp->link_watcher_id = 0; } mutex_exit(&dp->intrlock); } boolean_t gem_get_mac_addr_conf(struct gem_dev *dp) { char propname[32]; char *valstr; uint8_t mac[ETHERADDRL]; char *cp; int c; int i; int j; uint8_t v; uint8_t d; uint8_t ored; DPRINTF(3, (CE_CONT, "!%s: %s: called", dp->name, __func__)); /* * Get ethernet address from .conf file */ (void) sprintf(propname, "mac-addr"); if ((ddi_prop_lookup_string(DDI_DEV_T_ANY, dp->dip, DDI_PROP_DONTPASS, propname, &valstr)) != DDI_PROP_SUCCESS) { return (B_FALSE); } if (strlen(valstr) != ETHERADDRL*3-1) { goto syntax_err; } cp = valstr; j = 0; ored = 0; for (;;) { v = 0; for (i = 0; i < 2; i++) { c = *cp++; if (c >= 'a' && c <= 'f') { d = c - 'a' + 10; } else if (c >= 'A' && c <= 'F') { d = c - 'A' + 10; } else if (c >= '0' && c <= '9') { d = c - '0'; } else { goto syntax_err; } v = (v << 4) | d; } mac[j++] = v; ored |= v; if (j == ETHERADDRL) { /* done */ break; } c = *cp++; if (c != ':') { goto syntax_err; } } if (ored == 0) { goto err; } for (i = 0; i < ETHERADDRL; i++) { dp->dev_addr.ether_addr_octet[i] = mac[i]; } ddi_prop_free(valstr); return (B_TRUE); syntax_err: cmn_err(CE_CONT, "!%s: read mac addr: trying .conf: syntax err %s", dp->name, valstr); err: ddi_prop_free(valstr); return (B_FALSE); } /* ============================================================== */ /* * internal start/stop interface */ /* ============================================================== */ static int gem_mac_set_rx_filter(struct gem_dev *dp) { return ((*dp->gc.gc_set_rx_filter)(dp)); } /* * gem_mac_init: cold start */ static int gem_mac_init(struct gem_dev *dp) { DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); if (dp->mac_suspended) { return (GEM_FAILURE); } dp->mac_active = B_FALSE; gem_init_rx_ring(dp); gem_init_tx_ring(dp); /* reset transmitter state */ dp->tx_blocked = (clock_t)0; dp->tx_busy = 0; dp->tx_reclaim_busy = 0; dp->tx_max_packets = dp->gc.gc_tx_buf_limit; if ((*dp->gc.gc_init_chip)(dp) != GEM_SUCCESS) { return (GEM_FAILURE); } gem_prepare_rx_buf(dp); return (GEM_SUCCESS); } /* * gem_mac_start: warm start */ static int gem_mac_start(struct gem_dev *dp) { DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); ASSERT(mutex_owned(&dp->intrlock)); ASSERT(dp->nic_state == NIC_STATE_ONLINE); ASSERT(dp->mii_state == MII_STATE_LINKUP); /* enable tx and rx */ mutex_enter(&dp->xmitlock); if (dp->mac_suspended) { mutex_exit(&dp->xmitlock); return (GEM_FAILURE); } dp->mac_active = B_TRUE; mutex_exit(&dp->xmitlock); /* setup rx buffers */ (*dp->gc.gc_rx_start)(dp, SLOT(dp->rx_active_head, dp->gc.gc_rx_ring_size), dp->rx_active_tail - dp->rx_active_head); if ((*dp->gc.gc_start_chip)(dp) != GEM_SUCCESS) { cmn_err(CE_WARN, "%s: %s: start_chip: failed", dp->name, __func__); return (GEM_FAILURE); } mutex_enter(&dp->xmitlock); /* load untranmitted packets to the nic */ ASSERT(dp->tx_softq_tail - dp->tx_softq_head >= 0); if (dp->tx_softq_tail - dp->tx_softq_head > 0) { gem_tx_load_descs_oo(dp, dp->tx_softq_head, dp->tx_softq_tail, GEM_TXFLAG_HEAD); /* issue preloaded tx buffers */ gem_tx_start_unit(dp); } mutex_exit(&dp->xmitlock); return (GEM_SUCCESS); } static int gem_mac_stop(struct gem_dev *dp, uint_t flags) { int i; int wait_time; /* in uS */ #ifdef GEM_DEBUG_LEVEL clock_t now; #endif int ret = GEM_SUCCESS; DPRINTF(1, (CE_CONT, "!%s: %s: called, rx_buf_free:%d", dp->name, __func__, dp->rx_buf_freecnt)); ASSERT(mutex_owned(&dp->intrlock)); ASSERT(!mutex_owned(&dp->xmitlock)); /* * Block transmits */ mutex_enter(&dp->xmitlock); if (dp->mac_suspended) { mutex_exit(&dp->xmitlock); return (GEM_SUCCESS); } dp->mac_active = B_FALSE; while (dp->tx_busy > 0) { cv_wait(&dp->tx_drain_cv, &dp->xmitlock); } mutex_exit(&dp->xmitlock); if ((flags & GEM_RESTART_NOWAIT) == 0) { /* * Wait for all tx buffers sent. */ wait_time = 2 * (8 * MAXPKTBUF(dp) / gem_speed_value[dp->speed]) * (dp->tx_active_tail - dp->tx_active_head); DPRINTF(0, (CE_CONT, "%s: %s: max drain time: %d uS", dp->name, __func__, wait_time)); i = 0; #ifdef GEM_DEBUG_LEVEL now = ddi_get_lbolt(); #endif while (dp->tx_active_tail != dp->tx_active_head) { if (i > wait_time) { /* timeout */ cmn_err(CE_NOTE, "%s: %s timeout: tx drain", dp->name, __func__); break; } (void) gem_reclaim_txbuf(dp); drv_usecwait(100); i += 100; } DPRINTF(0, (CE_NOTE, "!%s: %s: the nic have drained in %d uS, real %d mS", dp->name, __func__, i, 10*((int)(ddi_get_lbolt() - now)))); } /* * Now we can stop the nic safely. */ if ((*dp->gc.gc_stop_chip)(dp) != GEM_SUCCESS) { cmn_err(CE_NOTE, "%s: %s: resetting the chip to stop it", dp->name, __func__); if ((*dp->gc.gc_reset_chip)(dp) != GEM_SUCCESS) { cmn_err(CE_WARN, "%s: %s: failed to reset chip", dp->name, __func__); } } /* * Clear all rx buffers */ if (flags & GEM_RESTART_KEEP_BUF) { (void) gem_receive(dp); } gem_clean_rx_buf(dp); /* * Update final statistics */ (*dp->gc.gc_get_stats)(dp); /* * Clear all pended tx packets */ ASSERT(dp->tx_active_tail == dp->tx_softq_head); ASSERT(dp->tx_softq_tail == dp->tx_free_head); if (flags & GEM_RESTART_KEEP_BUF) { /* restore active tx buffers */ dp->tx_active_tail = dp->tx_active_head; dp->tx_softq_head = dp->tx_active_head; } else { gem_clean_tx_buf(dp); } return (ret); } static int gem_add_multicast(struct gem_dev *dp, const uint8_t *ep) { int cnt; int err; DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); mutex_enter(&dp->intrlock); if (dp->mac_suspended) { mutex_exit(&dp->intrlock); return (GEM_FAILURE); } if (dp->mc_count_req++ < GEM_MAXMC) { /* append the new address at the end of the mclist */ cnt = dp->mc_count; bcopy(ep, dp->mc_list[cnt].addr.ether_addr_octet, ETHERADDRL); if (dp->gc.gc_multicast_hash) { dp->mc_list[cnt].hash = (*dp->gc.gc_multicast_hash)(dp, (uint8_t *)ep); } dp->mc_count = cnt + 1; } if (dp->mc_count_req != dp->mc_count) { /* multicast address list overflow */ dp->rxmode |= RXMODE_MULTI_OVF; } else { dp->rxmode &= ~RXMODE_MULTI_OVF; } /* tell new multicast list to the hardware */ err = gem_mac_set_rx_filter(dp); mutex_exit(&dp->intrlock); return (err); } static int gem_remove_multicast(struct gem_dev *dp, const uint8_t *ep) { size_t len; int i; int cnt; int err; DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); mutex_enter(&dp->intrlock); if (dp->mac_suspended) { mutex_exit(&dp->intrlock); return (GEM_FAILURE); } dp->mc_count_req--; cnt = dp->mc_count; for (i = 0; i < cnt; i++) { if (bcmp(ep, &dp->mc_list[i].addr, ETHERADDRL)) { continue; } /* shrink the mclist by copying forward */ len = (cnt - (i + 1)) * sizeof (*dp->mc_list); if (len > 0) { bcopy(&dp->mc_list[i+1], &dp->mc_list[i], len); } dp->mc_count--; break; } if (dp->mc_count_req != dp->mc_count) { /* multicast address list overflow */ dp->rxmode |= RXMODE_MULTI_OVF; } else { dp->rxmode &= ~RXMODE_MULTI_OVF; } /* In gem v2, don't hold xmitlock on calling set_rx_filter */ err = gem_mac_set_rx_filter(dp); mutex_exit(&dp->intrlock); return (err); } /* ============================================================== */ /* * ND interface */ /* ============================================================== */ enum { PARAM_AUTONEG_CAP, PARAM_PAUSE_CAP, PARAM_ASYM_PAUSE_CAP, PARAM_1000FDX_CAP, PARAM_1000HDX_CAP, PARAM_100T4_CAP, PARAM_100FDX_CAP, PARAM_100HDX_CAP, PARAM_10FDX_CAP, PARAM_10HDX_CAP, PARAM_ADV_AUTONEG_CAP, PARAM_ADV_PAUSE_CAP, PARAM_ADV_ASYM_PAUSE_CAP, PARAM_ADV_1000FDX_CAP, PARAM_ADV_1000HDX_CAP, PARAM_ADV_100T4_CAP, PARAM_ADV_100FDX_CAP, PARAM_ADV_100HDX_CAP, PARAM_ADV_10FDX_CAP, PARAM_ADV_10HDX_CAP, PARAM_LP_AUTONEG_CAP, PARAM_LP_PAUSE_CAP, PARAM_LP_ASYM_PAUSE_CAP, PARAM_LP_1000FDX_CAP, PARAM_LP_1000HDX_CAP, PARAM_LP_100T4_CAP, PARAM_LP_100FDX_CAP, PARAM_LP_100HDX_CAP, PARAM_LP_10FDX_CAP, PARAM_LP_10HDX_CAP, PARAM_LINK_STATUS, PARAM_LINK_SPEED, PARAM_LINK_DUPLEX, PARAM_LINK_AUTONEG, PARAM_LINK_RX_PAUSE, PARAM_LINK_TX_PAUSE, PARAM_LOOP_MODE, PARAM_MSI_CNT, #ifdef DEBUG_RESUME PARAM_RESUME_TEST, #endif PARAM_COUNT }; enum ioc_reply { IOC_INVAL = -1, /* bad, NAK with EINVAL */ IOC_DONE, /* OK, reply sent */ IOC_ACK, /* OK, just send ACK */ IOC_REPLY, /* OK, just send reply */ IOC_RESTART_ACK, /* OK, restart & ACK */ IOC_RESTART_REPLY /* OK, restart & reply */ }; struct gem_nd_arg { struct gem_dev *dp; int item; }; static int gem_param_get(queue_t *q, mblk_t *mp, caddr_t arg, cred_t *credp) { struct gem_dev *dp = ((struct gem_nd_arg *)(void *)arg)->dp; int item = ((struct gem_nd_arg *)(void *)arg)->item; long val; DPRINTF(0, (CE_CONT, "!%s: %s: called, item:%d", dp->name, __func__, item)); switch (item) { case PARAM_AUTONEG_CAP: val = BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG); DPRINTF(0, (CE_CONT, "autoneg_cap:%d", val)); break; case PARAM_PAUSE_CAP: val = BOOLEAN(dp->gc.gc_flow_control & 1); break; case PARAM_ASYM_PAUSE_CAP: val = BOOLEAN(dp->gc.gc_flow_control & 2); break; case PARAM_1000FDX_CAP: val = (dp->mii_xstatus & MII_XSTATUS_1000BASET_FD) || (dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD); break; case PARAM_1000HDX_CAP: val = (dp->mii_xstatus & MII_XSTATUS_1000BASET) || (dp->mii_xstatus & MII_XSTATUS_1000BASEX); break; case PARAM_100T4_CAP: val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4); break; case PARAM_100FDX_CAP: val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD); break; case PARAM_100HDX_CAP: val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX); break; case PARAM_10FDX_CAP: val = BOOLEAN(dp->mii_status & MII_STATUS_10_FD); break; case PARAM_10HDX_CAP: val = BOOLEAN(dp->mii_status & MII_STATUS_10); break; case PARAM_ADV_AUTONEG_CAP: val = dp->anadv_autoneg; break; case PARAM_ADV_PAUSE_CAP: val = BOOLEAN(dp->anadv_flow_control & 1); break; case PARAM_ADV_ASYM_PAUSE_CAP: val = BOOLEAN(dp->anadv_flow_control & 2); break; case PARAM_ADV_1000FDX_CAP: val = dp->anadv_1000fdx; break; case PARAM_ADV_1000HDX_CAP: val = dp->anadv_1000hdx; break; case PARAM_ADV_100T4_CAP: val = dp->anadv_100t4; break; case PARAM_ADV_100FDX_CAP: val = dp->anadv_100fdx; break; case PARAM_ADV_100HDX_CAP: val = dp->anadv_100hdx; break; case PARAM_ADV_10FDX_CAP: val = dp->anadv_10fdx; break; case PARAM_ADV_10HDX_CAP: val = dp->anadv_10hdx; break; case PARAM_LP_AUTONEG_CAP: val = BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN); break; case PARAM_LP_PAUSE_CAP: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_PAUSE); break; case PARAM_LP_ASYM_PAUSE_CAP: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_ASM_DIR); break; case PARAM_LP_1000FDX_CAP: val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_FULL); break; case PARAM_LP_1000HDX_CAP: val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_HALF); break; case PARAM_LP_100T4_CAP: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_T4); break; case PARAM_LP_100FDX_CAP: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX_FD); break; case PARAM_LP_100HDX_CAP: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX); break; case PARAM_LP_10FDX_CAP: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T_FD); break; case PARAM_LP_10HDX_CAP: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T); break; case PARAM_LINK_STATUS: val = (dp->mii_state == MII_STATE_LINKUP); break; case PARAM_LINK_SPEED: val = gem_speed_value[dp->speed]; break; case PARAM_LINK_DUPLEX: val = 0; if (dp->mii_state == MII_STATE_LINKUP) { val = dp->full_duplex ? 2 : 1; } break; case PARAM_LINK_AUTONEG: val = BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN); break; case PARAM_LINK_RX_PAUSE: val = (dp->flow_control == FLOW_CONTROL_SYMMETRIC) || (dp->flow_control == FLOW_CONTROL_RX_PAUSE); break; case PARAM_LINK_TX_PAUSE: val = (dp->flow_control == FLOW_CONTROL_SYMMETRIC) || (dp->flow_control == FLOW_CONTROL_TX_PAUSE); break; #ifdef DEBUG_RESUME case PARAM_RESUME_TEST: val = 0; break; #endif default: cmn_err(CE_WARN, "%s: unimplemented ndd control (%d)", dp->name, item); break; } (void) mi_mpprintf(mp, "%ld", val); return (0); } static int gem_param_set(queue_t *q, mblk_t *mp, char *value, caddr_t arg, cred_t *credp) { struct gem_dev *dp = ((struct gem_nd_arg *)(void *)arg)->dp; int item = ((struct gem_nd_arg *)(void *)arg)->item; long val; char *end; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); if (ddi_strtol(value, &end, 10, &val)) { return (EINVAL); } if (end == value) { return (EINVAL); } switch (item) { case PARAM_ADV_AUTONEG_CAP: if (val != 0 && val != 1) { goto err; } if (val && (dp->mii_status & MII_STATUS_CANAUTONEG) == 0) { goto err; } dp->anadv_autoneg = (int)val; break; case PARAM_ADV_PAUSE_CAP: if (val != 0 && val != 1) { goto err; } if (val) { dp->anadv_flow_control |= 1; } else { dp->anadv_flow_control &= ~1; } break; case PARAM_ADV_ASYM_PAUSE_CAP: if (val != 0 && val != 1) { goto err; } if (val) { dp->anadv_flow_control |= 2; } else { dp->anadv_flow_control &= ~2; } break; case PARAM_ADV_1000FDX_CAP: if (val != 0 && val != 1) { goto err; } if (val && (dp->mii_xstatus & (MII_XSTATUS_1000BASET_FD | MII_XSTATUS_1000BASEX_FD)) == 0) { goto err; } dp->anadv_1000fdx = (int)val; break; case PARAM_ADV_1000HDX_CAP: if (val != 0 && val != 1) { goto err; } if (val && (dp->mii_xstatus & (MII_XSTATUS_1000BASET | MII_XSTATUS_1000BASEX)) == 0) { goto err; } dp->anadv_1000hdx = (int)val; break; case PARAM_ADV_100T4_CAP: if (val != 0 && val != 1) { goto err; } if (val && (dp->mii_status & MII_STATUS_100_BASE_T4) == 0) { goto err; } dp->anadv_100t4 = (int)val; break; case PARAM_ADV_100FDX_CAP: if (val != 0 && val != 1) { goto err; } if (val && (dp->mii_status & MII_STATUS_100_BASEX_FD) == 0) { goto err; } dp->anadv_100fdx = (int)val; break; case PARAM_ADV_100HDX_CAP: if (val != 0 && val != 1) { goto err; } if (val && (dp->mii_status & MII_STATUS_100_BASEX) == 0) { goto err; } dp->anadv_100hdx = (int)val; break; case PARAM_ADV_10FDX_CAP: if (val != 0 && val != 1) { goto err; } if (val && (dp->mii_status & MII_STATUS_10_FD) == 0) { goto err; } dp->anadv_10fdx = (int)val; break; case PARAM_ADV_10HDX_CAP: if (val != 0 && val != 1) { goto err; } if (val && (dp->mii_status & MII_STATUS_10) == 0) { goto err; } dp->anadv_10hdx = (int)val; break; } /* sync with PHY */ gem_choose_forcedmode(dp); dp->mii_state = MII_STATE_UNKNOWN; if (dp->gc.gc_mii_hw_link_detection && dp->link_watcher_id == 0) { /* XXX - Can we ignore the return code ? */ (void) gem_mii_link_check(dp); } return (0); err: return (EINVAL); } static void gem_nd_load(struct gem_dev *dp, char *name, ndgetf_t gf, ndsetf_t sf, int item) { struct gem_nd_arg *arg; ASSERT(item >= 0); ASSERT(item < PARAM_COUNT); arg = &((struct gem_nd_arg *)(void *)dp->nd_arg_p)[item]; arg->dp = dp; arg->item = item; DPRINTF(2, (CE_CONT, "!%s: %s: name:%s, item:%d", dp->name, __func__, name, item)); (void) nd_load(&dp->nd_data_p, name, gf, sf, (caddr_t)arg); } static void gem_nd_setup(struct gem_dev *dp) { DPRINTF(0, (CE_CONT, "!%s: %s: called, mii_status:0x%b", dp->name, __func__, dp->mii_status, MII_STATUS_BITS)); ASSERT(dp->nd_arg_p == NULL); dp->nd_arg_p = kmem_zalloc(sizeof (struct gem_nd_arg) * PARAM_COUNT, KM_SLEEP); #define SETFUNC(x) ((x) ? gem_param_set : NULL) gem_nd_load(dp, "autoneg_cap", gem_param_get, NULL, PARAM_AUTONEG_CAP); gem_nd_load(dp, "pause_cap", gem_param_get, NULL, PARAM_PAUSE_CAP); gem_nd_load(dp, "asym_pause_cap", gem_param_get, NULL, PARAM_ASYM_PAUSE_CAP); gem_nd_load(dp, "1000fdx_cap", gem_param_get, NULL, PARAM_1000FDX_CAP); gem_nd_load(dp, "1000hdx_cap", gem_param_get, NULL, PARAM_1000HDX_CAP); gem_nd_load(dp, "100T4_cap", gem_param_get, NULL, PARAM_100T4_CAP); gem_nd_load(dp, "100fdx_cap", gem_param_get, NULL, PARAM_100FDX_CAP); gem_nd_load(dp, "100hdx_cap", gem_param_get, NULL, PARAM_100HDX_CAP); gem_nd_load(dp, "10fdx_cap", gem_param_get, NULL, PARAM_10FDX_CAP); gem_nd_load(dp, "10hdx_cap", gem_param_get, NULL, PARAM_10HDX_CAP); /* Our advertised capabilities */ gem_nd_load(dp, "adv_autoneg_cap", gem_param_get, SETFUNC(dp->mii_status & MII_STATUS_CANAUTONEG), PARAM_ADV_AUTONEG_CAP); gem_nd_load(dp, "adv_pause_cap", gem_param_get, SETFUNC(dp->gc.gc_flow_control & 1), PARAM_ADV_PAUSE_CAP); gem_nd_load(dp, "adv_asym_pause_cap", gem_param_get, SETFUNC(dp->gc.gc_flow_control & 2), PARAM_ADV_ASYM_PAUSE_CAP); gem_nd_load(dp, "adv_1000fdx_cap", gem_param_get, SETFUNC(dp->mii_xstatus & (MII_XSTATUS_1000BASEX_FD | MII_XSTATUS_1000BASET_FD)), PARAM_ADV_1000FDX_CAP); gem_nd_load(dp, "adv_1000hdx_cap", gem_param_get, SETFUNC(dp->mii_xstatus & (MII_XSTATUS_1000BASEX | MII_XSTATUS_1000BASET)), PARAM_ADV_1000HDX_CAP); gem_nd_load(dp, "adv_100T4_cap", gem_param_get, SETFUNC((dp->mii_status & MII_STATUS_100_BASE_T4) && !dp->mii_advert_ro), PARAM_ADV_100T4_CAP); gem_nd_load(dp, "adv_100fdx_cap", gem_param_get, SETFUNC((dp->mii_status & MII_STATUS_100_BASEX_FD) && !dp->mii_advert_ro), PARAM_ADV_100FDX_CAP); gem_nd_load(dp, "adv_100hdx_cap", gem_param_get, SETFUNC((dp->mii_status & MII_STATUS_100_BASEX) && !dp->mii_advert_ro), PARAM_ADV_100HDX_CAP); gem_nd_load(dp, "adv_10fdx_cap", gem_param_get, SETFUNC((dp->mii_status & MII_STATUS_10_FD) && !dp->mii_advert_ro), PARAM_ADV_10FDX_CAP); gem_nd_load(dp, "adv_10hdx_cap", gem_param_get, SETFUNC((dp->mii_status & MII_STATUS_10) && !dp->mii_advert_ro), PARAM_ADV_10HDX_CAP); /* Partner's advertised capabilities */ gem_nd_load(dp, "lp_autoneg_cap", gem_param_get, NULL, PARAM_LP_AUTONEG_CAP); gem_nd_load(dp, "lp_pause_cap", gem_param_get, NULL, PARAM_LP_PAUSE_CAP); gem_nd_load(dp, "lp_asym_pause_cap", gem_param_get, NULL, PARAM_LP_ASYM_PAUSE_CAP); gem_nd_load(dp, "lp_1000fdx_cap", gem_param_get, NULL, PARAM_LP_1000FDX_CAP); gem_nd_load(dp, "lp_1000hdx_cap", gem_param_get, NULL, PARAM_LP_1000HDX_CAP); gem_nd_load(dp, "lp_100T4_cap", gem_param_get, NULL, PARAM_LP_100T4_CAP); gem_nd_load(dp, "lp_100fdx_cap", gem_param_get, NULL, PARAM_LP_100FDX_CAP); gem_nd_load(dp, "lp_100hdx_cap", gem_param_get, NULL, PARAM_LP_100HDX_CAP); gem_nd_load(dp, "lp_10fdx_cap", gem_param_get, NULL, PARAM_LP_10FDX_CAP); gem_nd_load(dp, "lp_10hdx_cap", gem_param_get, NULL, PARAM_LP_10HDX_CAP); /* Current operating modes */ gem_nd_load(dp, "link_status", gem_param_get, NULL, PARAM_LINK_STATUS); gem_nd_load(dp, "link_speed", gem_param_get, NULL, PARAM_LINK_SPEED); gem_nd_load(dp, "link_duplex", gem_param_get, NULL, PARAM_LINK_DUPLEX); gem_nd_load(dp, "link_autoneg", gem_param_get, NULL, PARAM_LINK_AUTONEG); gem_nd_load(dp, "link_rx_pause", gem_param_get, NULL, PARAM_LINK_RX_PAUSE); gem_nd_load(dp, "link_tx_pause", gem_param_get, NULL, PARAM_LINK_TX_PAUSE); #ifdef DEBUG_RESUME gem_nd_load(dp, "resume_test", gem_param_get, NULL, PARAM_RESUME_TEST); #endif #undef SETFUNC } static enum ioc_reply gem_nd_ioctl(struct gem_dev *dp, queue_t *wq, mblk_t *mp, struct iocblk *iocp) { boolean_t ok; ASSERT(mutex_owned(&dp->intrlock)); DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); switch (iocp->ioc_cmd) { case ND_GET: ok = nd_getset(wq, dp->nd_data_p, mp); DPRINTF(0, (CE_CONT, "%s: get %s", dp->name, ok ? "OK" : "FAIL")); return (ok ? IOC_REPLY : IOC_INVAL); case ND_SET: ok = nd_getset(wq, dp->nd_data_p, mp); DPRINTF(0, (CE_CONT, "%s: set %s err %d", dp->name, ok ? "OK" : "FAIL", iocp->ioc_error)); if (!ok) { return (IOC_INVAL); } if (iocp->ioc_error) { return (IOC_REPLY); } return (IOC_RESTART_REPLY); } cmn_err(CE_WARN, "%s: invalid cmd 0x%x", dp->name, iocp->ioc_cmd); return (IOC_INVAL); } static void gem_nd_cleanup(struct gem_dev *dp) { ASSERT(dp->nd_data_p != NULL); ASSERT(dp->nd_arg_p != NULL); nd_free(&dp->nd_data_p); kmem_free(dp->nd_arg_p, sizeof (struct gem_nd_arg) * PARAM_COUNT); dp->nd_arg_p = NULL; } static void gem_mac_ioctl(struct gem_dev *dp, queue_t *wq, mblk_t *mp) { struct iocblk *iocp; enum ioc_reply status; int cmd; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); /* * Validate the command before bothering with the mutex ... */ iocp = (void *)mp->b_rptr; iocp->ioc_error = 0; cmd = iocp->ioc_cmd; DPRINTF(0, (CE_CONT, "%s: %s cmd:0x%x", dp->name, __func__, cmd)); mutex_enter(&dp->intrlock); mutex_enter(&dp->xmitlock); switch (cmd) { default: _NOTE(NOTREACHED) status = IOC_INVAL; break; case ND_GET: case ND_SET: status = gem_nd_ioctl(dp, wq, mp, iocp); break; } mutex_exit(&dp->xmitlock); mutex_exit(&dp->intrlock); #ifdef DEBUG_RESUME if (cmd == ND_GET) { gem_suspend(dp->dip); gem_resume(dp->dip); } #endif /* * Finally, decide how to reply */ switch (status) { default: case IOC_INVAL: /* * Error, reply with a NAK and EINVAL or the specified error */ miocnak(wq, mp, 0, iocp->ioc_error == 0 ? EINVAL : iocp->ioc_error); break; case IOC_DONE: /* * OK, reply already sent */ break; case IOC_RESTART_ACK: case IOC_ACK: /* * OK, reply with an ACK */ miocack(wq, mp, 0, 0); break; case IOC_RESTART_REPLY: case IOC_REPLY: /* * OK, send prepared reply as ACK or NAK */ mp->b_datap->db_type = iocp->ioc_error == 0 ? M_IOCACK : M_IOCNAK; qreply(wq, mp); break; } } #ifndef SYS_MAC_H #define XCVR_UNDEFINED 0 #define XCVR_NONE 1 #define XCVR_10 2 #define XCVR_100T4 3 #define XCVR_100X 4 #define XCVR_100T2 5 #define XCVR_1000X 6 #define XCVR_1000T 7 #endif static int gem_mac_xcvr_inuse(struct gem_dev *dp) { int val = XCVR_UNDEFINED; if ((dp->mii_status & MII_STATUS_XSTATUS) == 0) { if (dp->mii_status & MII_STATUS_100_BASE_T4) { val = XCVR_100T4; } else if (dp->mii_status & (MII_STATUS_100_BASEX_FD | MII_STATUS_100_BASEX)) { val = XCVR_100X; } else if (dp->mii_status & (MII_STATUS_100_BASE_T2_FD | MII_STATUS_100_BASE_T2)) { val = XCVR_100T2; } else if (dp->mii_status & (MII_STATUS_10_FD | MII_STATUS_10)) { val = XCVR_10; } } else if (dp->mii_xstatus & (MII_XSTATUS_1000BASET_FD | MII_XSTATUS_1000BASET)) { val = XCVR_1000T; } else if (dp->mii_xstatus & (MII_XSTATUS_1000BASEX_FD | MII_XSTATUS_1000BASEX)) { val = XCVR_1000X; } return (val); } /* ============================================================== */ /* * GLDv3 interface */ /* ============================================================== */ static int gem_m_getstat(void *, uint_t, uint64_t *); static int gem_m_start(void *); static void gem_m_stop(void *); static int gem_m_setpromisc(void *, boolean_t); static int gem_m_multicst(void *, boolean_t, const uint8_t *); static int gem_m_unicst(void *, const uint8_t *); static mblk_t *gem_m_tx(void *, mblk_t *); static void gem_m_resources(void *); static void gem_m_ioctl(void *, queue_t *, mblk_t *); static boolean_t gem_m_getcapab(void *, mac_capab_t, void *); #define GEM_M_CALLBACK_FLAGS (MC_RESOURCES | MC_IOCTL | MC_GETCAPAB) static mac_callbacks_t gem_m_callbacks = { GEM_M_CALLBACK_FLAGS, gem_m_getstat, gem_m_start, gem_m_stop, gem_m_setpromisc, gem_m_multicst, gem_m_unicst, gem_m_tx, gem_m_resources, gem_m_ioctl, gem_m_getcapab, }; static int gem_m_start(void *arg) { int err = 0; struct gem_dev *dp = arg; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); mutex_enter(&dp->intrlock); if (dp->mac_suspended) { err = EIO; goto x; } if (gem_mac_init(dp) != GEM_SUCCESS) { err = EIO; goto x; } dp->nic_state = NIC_STATE_INITIALIZED; /* reset rx filter state */ dp->mc_count = 0; dp->mc_count_req = 0; /* setup media mode if the link have been up */ if (dp->mii_state == MII_STATE_LINKUP) { (dp->gc.gc_set_media)(dp); } /* setup initial rx filter */ bcopy(dp->dev_addr.ether_addr_octet, dp->cur_addr.ether_addr_octet, ETHERADDRL); dp->rxmode |= RXMODE_ENABLE; if (gem_mac_set_rx_filter(dp) != GEM_SUCCESS) { err = EIO; goto x; } dp->nic_state = NIC_STATE_ONLINE; if (dp->mii_state == MII_STATE_LINKUP) { if (gem_mac_start(dp) != GEM_SUCCESS) { err = EIO; goto x; } } dp->timeout_id = timeout((void (*)(void *))gem_tx_timeout, (void *)dp, dp->gc.gc_tx_timeout_interval); mutex_exit(&dp->intrlock); return (0); x: dp->nic_state = NIC_STATE_STOPPED; mutex_exit(&dp->intrlock); return (err); } static void gem_m_stop(void *arg) { struct gem_dev *dp = arg; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); /* stop rx */ mutex_enter(&dp->intrlock); if (dp->mac_suspended) { mutex_exit(&dp->intrlock); return; } dp->rxmode &= ~RXMODE_ENABLE; (void) gem_mac_set_rx_filter(dp); mutex_exit(&dp->intrlock); /* stop tx timeout watcher */ if (dp->timeout_id) { while (untimeout(dp->timeout_id) == -1) ; dp->timeout_id = 0; } /* make the nic state inactive */ mutex_enter(&dp->intrlock); if (dp->mac_suspended) { mutex_exit(&dp->intrlock); return; } dp->nic_state = NIC_STATE_STOPPED; /* we need deassert mac_active due to block interrupt handler */ mutex_enter(&dp->xmitlock); dp->mac_active = B_FALSE; mutex_exit(&dp->xmitlock); /* block interrupts */ while (dp->intr_busy) { cv_wait(&dp->tx_drain_cv, &dp->intrlock); } (void) gem_mac_stop(dp, 0); mutex_exit(&dp->intrlock); } static int gem_m_multicst(void *arg, boolean_t add, const uint8_t *ep) { int err; int ret; struct gem_dev *dp = arg; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); if (add) { ret = gem_add_multicast(dp, ep); } else { ret = gem_remove_multicast(dp, ep); } err = 0; if (ret != GEM_SUCCESS) { err = EIO; } return (err); } static int gem_m_setpromisc(void *arg, boolean_t on) { int err = 0; /* no error */ struct gem_dev *dp = arg; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); mutex_enter(&dp->intrlock); if (dp->mac_suspended) { mutex_exit(&dp->intrlock); return (EIO); } if (on) { dp->rxmode |= RXMODE_PROMISC; } else { dp->rxmode &= ~RXMODE_PROMISC; } if (gem_mac_set_rx_filter(dp) != GEM_SUCCESS) { err = EIO; } mutex_exit(&dp->intrlock); return (err); } int gem_m_getstat(void *arg, uint_t stat, uint64_t *valp) { struct gem_dev *dp = arg; struct gem_stats *gstp = &dp->stats; uint64_t val = 0; DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); if (mutex_owned(&dp->intrlock)) { if (dp->mac_suspended) { return (EIO); } } else { mutex_enter(&dp->intrlock); if (dp->mac_suspended) { mutex_exit(&dp->intrlock); return (EIO); } mutex_exit(&dp->intrlock); } if ((*dp->gc.gc_get_stats)(dp) != GEM_SUCCESS) { return (EIO); } switch (stat) { case MAC_STAT_IFSPEED: val = gem_speed_value[dp->speed] *1000000ull; break; case MAC_STAT_MULTIRCV: val = gstp->rmcast; break; case MAC_STAT_BRDCSTRCV: val = gstp->rbcast; break; case MAC_STAT_MULTIXMT: val = gstp->omcast; break; case MAC_STAT_BRDCSTXMT: val = gstp->obcast; break; case MAC_STAT_NORCVBUF: val = gstp->norcvbuf + gstp->missed; break; case MAC_STAT_IERRORS: val = gstp->errrcv; break; case MAC_STAT_NOXMTBUF: val = gstp->noxmtbuf; break; case MAC_STAT_OERRORS: val = gstp->errxmt; break; case MAC_STAT_COLLISIONS: val = gstp->collisions; break; case MAC_STAT_RBYTES: val = gstp->rbytes; break; case MAC_STAT_IPACKETS: val = gstp->rpackets; break; case MAC_STAT_OBYTES: val = gstp->obytes; break; case MAC_STAT_OPACKETS: val = gstp->opackets; break; case MAC_STAT_UNDERFLOWS: val = gstp->underflow; break; case MAC_STAT_OVERFLOWS: val = gstp->overflow; break; case ETHER_STAT_ALIGN_ERRORS: val = gstp->frame; break; case ETHER_STAT_FCS_ERRORS: val = gstp->crc; break; case ETHER_STAT_FIRST_COLLISIONS: val = gstp->first_coll; break; case ETHER_STAT_MULTI_COLLISIONS: val = gstp->multi_coll; break; case ETHER_STAT_SQE_ERRORS: val = gstp->sqe; break; case ETHER_STAT_DEFER_XMTS: val = gstp->defer; break; case ETHER_STAT_TX_LATE_COLLISIONS: val = gstp->xmtlatecoll; break; case ETHER_STAT_EX_COLLISIONS: val = gstp->excoll; break; case ETHER_STAT_MACXMT_ERRORS: val = gstp->xmit_internal_err; break; case ETHER_STAT_CARRIER_ERRORS: val = gstp->nocarrier; break; case ETHER_STAT_TOOLONG_ERRORS: val = gstp->frame_too_long; break; case ETHER_STAT_MACRCV_ERRORS: val = gstp->rcv_internal_err; break; case ETHER_STAT_XCVR_ADDR: val = dp->mii_phy_addr; break; case ETHER_STAT_XCVR_ID: val = dp->mii_phy_id; break; case ETHER_STAT_XCVR_INUSE: val = gem_mac_xcvr_inuse(dp); break; case ETHER_STAT_CAP_1000FDX: val = (dp->mii_xstatus & MII_XSTATUS_1000BASET_FD) || (dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD); break; case ETHER_STAT_CAP_1000HDX: val = (dp->mii_xstatus & MII_XSTATUS_1000BASET) || (dp->mii_xstatus & MII_XSTATUS_1000BASEX); break; case ETHER_STAT_CAP_100FDX: val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD); break; case ETHER_STAT_CAP_100HDX: val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX); break; case ETHER_STAT_CAP_10FDX: val = BOOLEAN(dp->mii_status & MII_STATUS_10_FD); break; case ETHER_STAT_CAP_10HDX: val = BOOLEAN(dp->mii_status & MII_STATUS_10); break; case ETHER_STAT_CAP_ASMPAUSE: val = BOOLEAN(dp->gc.gc_flow_control & 2); break; case ETHER_STAT_CAP_PAUSE: val = BOOLEAN(dp->gc.gc_flow_control & 1); break; case ETHER_STAT_CAP_AUTONEG: val = BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG); break; case ETHER_STAT_ADV_CAP_1000FDX: val = dp->anadv_1000fdx; break; case ETHER_STAT_ADV_CAP_1000HDX: val = dp->anadv_1000hdx; break; case ETHER_STAT_ADV_CAP_100FDX: val = dp->anadv_100fdx; break; case ETHER_STAT_ADV_CAP_100HDX: val = dp->anadv_100hdx; break; case ETHER_STAT_ADV_CAP_10FDX: val = dp->anadv_10fdx; break; case ETHER_STAT_ADV_CAP_10HDX: val = dp->anadv_10hdx; break; case ETHER_STAT_ADV_CAP_ASMPAUSE: val = BOOLEAN(dp->anadv_flow_control & 2); break; case ETHER_STAT_ADV_CAP_PAUSE: val = BOOLEAN(dp->anadv_flow_control & 1); break; case ETHER_STAT_ADV_CAP_AUTONEG: val = dp->anadv_autoneg; break; case ETHER_STAT_LP_CAP_1000FDX: val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_FULL); break; case ETHER_STAT_LP_CAP_1000HDX: val = BOOLEAN(dp->mii_stat1000 & MII_1000TS_LP_HALF); break; case ETHER_STAT_LP_CAP_100FDX: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX_FD); break; case ETHER_STAT_LP_CAP_100HDX: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_TX); break; case ETHER_STAT_LP_CAP_10FDX: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T_FD); break; case ETHER_STAT_LP_CAP_10HDX: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_10BASE_T); break; case ETHER_STAT_LP_CAP_ASMPAUSE: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_ASM_DIR); break; case ETHER_STAT_LP_CAP_PAUSE: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_PAUSE); break; case ETHER_STAT_LP_CAP_AUTONEG: val = BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN); break; case ETHER_STAT_LINK_ASMPAUSE: val = BOOLEAN(dp->flow_control & 2); break; case ETHER_STAT_LINK_PAUSE: val = BOOLEAN(dp->flow_control & 1); break; case ETHER_STAT_LINK_AUTONEG: val = dp->anadv_autoneg && BOOLEAN(dp->mii_exp & MII_AN_EXP_LPCANAN); break; case ETHER_STAT_LINK_DUPLEX: val = (dp->mii_state == MII_STATE_LINKUP) ? (dp->full_duplex ? 2 : 1) : 0; break; case ETHER_STAT_TOOSHORT_ERRORS: val = gstp->runt; break; case ETHER_STAT_LP_REMFAULT: val = BOOLEAN(dp->mii_lpable & MII_AN_ADVERT_REMFAULT); break; case ETHER_STAT_JABBER_ERRORS: val = gstp->jabber; break; case ETHER_STAT_CAP_100T4: val = BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4); break; case ETHER_STAT_ADV_CAP_100T4: val = dp->anadv_100t4; break; case ETHER_STAT_LP_CAP_100T4: val = BOOLEAN(dp->mii_lpable & MII_ABILITY_100BASE_T4); break; default: #if GEM_DEBUG_LEVEL > 2 cmn_err(CE_WARN, "%s: unrecognized parameter value = %d", __func__, stat); #endif return (ENOTSUP); } *valp = val; return (0); } static int gem_m_unicst(void *arg, const uint8_t *mac) { int err = 0; struct gem_dev *dp = arg; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); mutex_enter(&dp->intrlock); if (dp->mac_suspended) { mutex_exit(&dp->intrlock); return (EIO); } bcopy(mac, dp->cur_addr.ether_addr_octet, ETHERADDRL); dp->rxmode |= RXMODE_ENABLE; if (gem_mac_set_rx_filter(dp) != GEM_SUCCESS) { err = EIO; } mutex_exit(&dp->intrlock); return (err); } /* * gem_m_tx is used only for sending data packets into ethernet wire. */ static mblk_t * gem_m_tx(void *arg, mblk_t *mp) { uint32_t flags = 0; struct gem_dev *dp = arg; mblk_t *tp; DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); ASSERT(dp->nic_state == NIC_STATE_ONLINE); if (dp->mii_state != MII_STATE_LINKUP) { /* Some nics hate to send packets when the link is down. */ while (mp) { tp = mp->b_next; mp->b_next = NULL; freemsg(mp); mp = tp; } return (NULL); } return (gem_send_common(dp, mp, flags)); } static void gem_set_coalease(void *arg, time_t ticks, uint_t count) { struct gem_dev *dp = arg; DPRINTF(1, (CE_CONT, "%s: %s: ticks:%d count:%d", dp->name, __func__, ticks, count)); mutex_enter(&dp->intrlock); dp->poll_pkt_delay = min(count, dp->gc.gc_rx_ring_size/2); mutex_exit(&dp->intrlock); } static void gem_m_resources(void *arg) { struct gem_dev *dp = arg; mac_rx_fifo_t mrf; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); mutex_enter(&dp->intrlock); mutex_enter(&dp->xmitlock); /* * Register Rx rings as resources and save mac * resource id for future reference */ mrf.mrf_type = MAC_RX_FIFO; mrf.mrf_blank = gem_set_coalease; mrf.mrf_arg = (void *)dp; mrf.mrf_normal_blank_time = 1; /* in uS */ mrf.mrf_normal_pkt_count = dp->poll_pkt_delay; dp->mac_rx_ring_ha = mac_resource_add(dp->mh, (mac_resource_t *)&mrf); mutex_exit(&dp->xmitlock); mutex_exit(&dp->intrlock); } static void gem_m_ioctl(void *arg, queue_t *wq, mblk_t *mp) { DPRINTF(0, (CE_CONT, "!%s: %s: called", ((struct gem_dev *)arg)->name, __func__)); gem_mac_ioctl((struct gem_dev *)arg, wq, mp); } static boolean_t gem_m_getcapab(void *arg, mac_capab_t cap, void *cap_data) { boolean_t ret; ret = B_FALSE; switch (cap) { case MAC_CAPAB_POLL: ret = B_TRUE; break; } return (ret); } static void gem_gld3_init(struct gem_dev *dp, mac_register_t *macp) { macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER; macp->m_driver = dp; macp->m_dip = dp->dip; macp->m_src_addr = dp->dev_addr.ether_addr_octet; macp->m_callbacks = &gem_m_callbacks; macp->m_min_sdu = 0; macp->m_max_sdu = dp->mtu; if (dp->misc_flag & GEM_VLAN) { macp->m_margin = VTAG_SIZE; } } /* ======================================================================== */ /* * attach/detatch support */ /* ======================================================================== */ static void gem_read_conf(struct gem_dev *dp) { int val; DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); /* * Get media mode infomation from .conf file */ dp->anadv_autoneg = gem_prop_get_int(dp, "adv_autoneg_cap", 1) != 0; dp->anadv_1000fdx = gem_prop_get_int(dp, "adv_1000fdx_cap", 1) != 0; dp->anadv_1000hdx = gem_prop_get_int(dp, "adv_1000hdx_cap", 1) != 0; dp->anadv_100t4 = gem_prop_get_int(dp, "adv_100T4_cap", 1) != 0; dp->anadv_100fdx = gem_prop_get_int(dp, "adv_100fdx_cap", 1) != 0; dp->anadv_100hdx = gem_prop_get_int(dp, "adv_100hdx_cap", 1) != 0; dp->anadv_10fdx = gem_prop_get_int(dp, "adv_10fdx_cap", 1) != 0; dp->anadv_10hdx = gem_prop_get_int(dp, "adv_10hdx_cap", 1) != 0; if ((ddi_prop_exists(DDI_DEV_T_ANY, dp->dip, DDI_PROP_DONTPASS, "full-duplex"))) { dp->full_duplex = gem_prop_get_int(dp, "full-duplex", 1) != 0; dp->anadv_autoneg = B_FALSE; if (dp->full_duplex) { dp->anadv_1000hdx = B_FALSE; dp->anadv_100hdx = B_FALSE; dp->anadv_10hdx = B_FALSE; } else { dp->anadv_1000fdx = B_FALSE; dp->anadv_100fdx = B_FALSE; dp->anadv_10fdx = B_FALSE; } } if ((val = gem_prop_get_int(dp, "speed", 0)) > 0) { dp->anadv_autoneg = B_FALSE; switch (val) { case 1000: dp->speed = GEM_SPD_1000; dp->anadv_100t4 = B_FALSE; dp->anadv_100fdx = B_FALSE; dp->anadv_100hdx = B_FALSE; dp->anadv_10fdx = B_FALSE; dp->anadv_10hdx = B_FALSE; break; case 100: dp->speed = GEM_SPD_100; dp->anadv_1000fdx = B_FALSE; dp->anadv_1000hdx = B_FALSE; dp->anadv_10fdx = B_FALSE; dp->anadv_10hdx = B_FALSE; break; case 10: dp->speed = GEM_SPD_10; dp->anadv_1000fdx = B_FALSE; dp->anadv_1000hdx = B_FALSE; dp->anadv_100t4 = B_FALSE; dp->anadv_100fdx = B_FALSE; dp->anadv_100hdx = B_FALSE; break; default: cmn_err(CE_WARN, "!%s: property %s: illegal value:%d", dp->name, "speed", val); dp->anadv_autoneg = B_TRUE; break; } } val = gem_prop_get_int(dp, "flow-control", dp->gc.gc_flow_control); if (val > FLOW_CONTROL_RX_PAUSE || val < FLOW_CONTROL_NONE) { cmn_err(CE_WARN, "!%s: property %s: illegal value:%d", dp->name, "flow-control", val); } else { val = min(val, dp->gc.gc_flow_control); } dp->anadv_flow_control = val; if (gem_prop_get_int(dp, "nointr", 0)) { dp->misc_flag |= GEM_NOINTR; cmn_err(CE_NOTE, "!%s: polling mode enabled", dp->name); } dp->mtu = gem_prop_get_int(dp, "mtu", dp->mtu); dp->txthr = gem_prop_get_int(dp, "txthr", dp->txthr); dp->rxthr = gem_prop_get_int(dp, "rxthr", dp->rxthr); dp->txmaxdma = gem_prop_get_int(dp, "txmaxdma", dp->txmaxdma); dp->rxmaxdma = gem_prop_get_int(dp, "rxmaxdma", dp->rxmaxdma); } /* * Gem kstat support */ #define GEM_LOCAL_DATA_SIZE(gc) \ (sizeof (struct gem_dev) + \ sizeof (struct mcast_addr) * GEM_MAXMC + \ sizeof (struct txbuf) * ((gc)->gc_tx_buf_size) + \ sizeof (void *) * ((gc)->gc_tx_buf_size)) struct gem_dev * gem_do_attach(dev_info_t *dip, int port, struct gem_conf *gc, void *base, ddi_acc_handle_t *regs_handlep, void *lp, int lmsize) { struct gem_dev *dp; int i; ddi_iblock_cookie_t c; mac_register_t *macp = NULL; int ret; int unit; int nports; unit = ddi_get_instance(dip); if ((nports = gc->gc_nports) == 0) { nports = 1; } if (nports == 1) { ddi_set_driver_private(dip, NULL); } DPRINTF(2, (CE_CONT, "!gem%d: gem_do_attach: called cmd:ATTACH", unit)); /* * Allocate soft data structure */ dp = kmem_zalloc(GEM_LOCAL_DATA_SIZE(gc), KM_SLEEP); if ((macp = mac_alloc(MAC_VERSION)) == NULL) { cmn_err(CE_WARN, "!gem%d: %s: mac_alloc failed", unit, __func__); return (NULL); } /* ddi_set_driver_private(dip, dp); */ /* link to private area */ dp->private = lp; dp->priv_size = lmsize; dp->mc_list = (struct mcast_addr *)&dp[1]; dp->dip = dip; (void) sprintf(dp->name, gc->gc_name, nports * unit + port); /* * Get iblock cookie */ if (ddi_get_iblock_cookie(dip, 0, &c) != DDI_SUCCESS) { cmn_err(CE_CONT, "!%s: gem_do_attach: ddi_get_iblock_cookie: failed", dp->name); goto err_free_private; } dp->iblock_cookie = c; /* * Initialize mutex's for this device. */ mutex_init(&dp->intrlock, NULL, MUTEX_DRIVER, (void *)c); mutex_init(&dp->xmitlock, NULL, MUTEX_DRIVER, (void *)c); cv_init(&dp->tx_drain_cv, NULL, CV_DRIVER, NULL); /* * configure gem parameter */ dp->base_addr = base; dp->regs_handle = *regs_handlep; dp->gc = *gc; gc = &dp->gc; /* patch for simplify dma resource management */ gc->gc_tx_max_frags = 1; gc->gc_tx_max_descs_per_pkt = 1; gc->gc_tx_ring_size = gc->gc_tx_buf_size; gc->gc_tx_ring_limit = gc->gc_tx_buf_limit; gc->gc_tx_desc_write_oo = B_TRUE; gc->gc_nports = nports; /* fix nports */ /* fix copy threadsholds */ gc->gc_tx_copy_thresh = max(ETHERMIN, gc->gc_tx_copy_thresh); gc->gc_rx_copy_thresh = max(ETHERMIN, gc->gc_rx_copy_thresh); /* fix rx buffer boundary for iocache line size */ ASSERT(gc->gc_dma_attr_txbuf.dma_attr_align-1 == gc->gc_tx_buf_align); ASSERT(gc->gc_dma_attr_rxbuf.dma_attr_align-1 == gc->gc_rx_buf_align); gc->gc_rx_buf_align = max(gc->gc_rx_buf_align, IOC_LINESIZE - 1); gc->gc_dma_attr_rxbuf.dma_attr_align = gc->gc_rx_buf_align + 1; /* fix descriptor boundary for cache line size */ gc->gc_dma_attr_desc.dma_attr_align = max(gc->gc_dma_attr_desc.dma_attr_align, IOC_LINESIZE); /* patch get_packet method */ if (gc->gc_get_packet == NULL) { gc->gc_get_packet = &gem_get_packet_default; } /* patch get_rx_start method */ if (gc->gc_rx_start == NULL) { gc->gc_rx_start = &gem_rx_start_default; } /* calculate descriptor area */ if (gc->gc_rx_desc_unit_shift >= 0) { dp->rx_desc_size = ROUNDUP(gc->gc_rx_ring_size << gc->gc_rx_desc_unit_shift, gc->gc_dma_attr_desc.dma_attr_align); } if (gc->gc_tx_desc_unit_shift >= 0) { dp->tx_desc_size = ROUNDUP(gc->gc_tx_ring_size << gc->gc_tx_desc_unit_shift, gc->gc_dma_attr_desc.dma_attr_align); } dp->mtu = ETHERMTU; dp->tx_buf = (void *)&dp->mc_list[GEM_MAXMC]; /* link tx buffers */ for (i = 0; i < dp->gc.gc_tx_buf_size; i++) { dp->tx_buf[i].txb_next = &dp->tx_buf[SLOT(i + 1, dp->gc.gc_tx_buf_size)]; } dp->rxmode = 0; dp->speed = GEM_SPD_10; /* default is 10Mbps */ dp->full_duplex = B_FALSE; /* default is half */ dp->flow_control = FLOW_CONTROL_NONE; dp->poll_pkt_delay = 8; /* typical coalease for rx packets */ /* performance tuning parameters */ dp->txthr = ETHERMAX; /* tx fifo threshold */ dp->txmaxdma = 16*4; /* tx max dma burst size */ dp->rxthr = 128; /* rx fifo threshold */ dp->rxmaxdma = 16*4; /* rx max dma burst size */ /* * Get media mode information from .conf file */ gem_read_conf(dp); /* rx_buf_len is required buffer length without padding for alignment */ dp->rx_buf_len = MAXPKTBUF(dp) + dp->gc.gc_rx_header_len; /* * Reset the chip */ mutex_enter(&dp->intrlock); dp->nic_state = NIC_STATE_STOPPED; ret = (*dp->gc.gc_reset_chip)(dp); mutex_exit(&dp->intrlock); if (ret != GEM_SUCCESS) { goto err_free_regs; } /* * HW dependant paremeter initialization */ mutex_enter(&dp->intrlock); ret = (*dp->gc.gc_attach_chip)(dp); mutex_exit(&dp->intrlock); if (ret != GEM_SUCCESS) { goto err_free_regs; } #ifdef DEBUG_MULTIFRAGS dp->gc.gc_tx_copy_thresh = dp->mtu; #endif /* allocate tx and rx resources */ if (gem_alloc_memory(dp)) { goto err_free_regs; } DPRINTF(0, (CE_CONT, "!%s: at 0x%x, %02x:%02x:%02x:%02x:%02x:%02x", dp->name, (long)dp->base_addr, dp->dev_addr.ether_addr_octet[0], dp->dev_addr.ether_addr_octet[1], dp->dev_addr.ether_addr_octet[2], dp->dev_addr.ether_addr_octet[3], dp->dev_addr.ether_addr_octet[4], dp->dev_addr.ether_addr_octet[5])); /* copy mac address */ dp->cur_addr = dp->dev_addr; gem_gld3_init(dp, macp); /* Probe MII phy (scan phy) */ dp->mii_lpable = 0; dp->mii_advert = 0; dp->mii_exp = 0; dp->mii_ctl1000 = 0; dp->mii_stat1000 = 0; if ((*dp->gc.gc_mii_probe)(dp) != GEM_SUCCESS) { goto err_free_ring; } /* mask unsupported abilities */ dp->anadv_autoneg &= BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG); dp->anadv_1000fdx &= BOOLEAN(dp->mii_xstatus & (MII_XSTATUS_1000BASEX_FD | MII_XSTATUS_1000BASET_FD)); dp->anadv_1000hdx &= BOOLEAN(dp->mii_xstatus & (MII_XSTATUS_1000BASEX | MII_XSTATUS_1000BASET)); dp->anadv_100t4 &= BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4); dp->anadv_100fdx &= BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD); dp->anadv_100hdx &= BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX); dp->anadv_10fdx &= BOOLEAN(dp->mii_status & MII_STATUS_10_FD); dp->anadv_10hdx &= BOOLEAN(dp->mii_status & MII_STATUS_10); gem_choose_forcedmode(dp); /* initialize MII phy if required */ if (dp->gc.gc_mii_init) { if ((*dp->gc.gc_mii_init)(dp) != GEM_SUCCESS) { goto err_free_ring; } } /* * initialize kstats including mii statistics */ gem_nd_setup(dp); /* * Add interrupt to system. */ if (ret = mac_register(macp, &dp->mh)) { cmn_err(CE_WARN, "!%s: mac_register failed, error:%d", dp->name, ret); goto err_release_stats; } mac_free(macp); macp = NULL; if (dp->misc_flag & GEM_SOFTINTR) { if (ddi_add_softintr(dip, DDI_SOFTINT_LOW, &dp->soft_id, NULL, NULL, (uint_t (*)(caddr_t))gem_intr, (caddr_t)dp) != DDI_SUCCESS) { cmn_err(CE_WARN, "!%s: ddi_add_softintr failed", dp->name); goto err_unregister; } } else if ((dp->misc_flag & GEM_NOINTR) == 0) { if (ddi_add_intr(dip, 0, NULL, NULL, (uint_t (*)(caddr_t))gem_intr, (caddr_t)dp) != DDI_SUCCESS) { cmn_err(CE_WARN, "!%s: ddi_add_intr failed", dp->name); goto err_unregister; } } else { /* * Dont use interrupt. * schedule first call of gem_intr_watcher */ dp->intr_watcher_id = timeout((void (*)(void *))gem_intr_watcher, (void *)dp, drv_usectohz(3*1000000)); } /* link this device to dev_info */ dp->next = (struct gem_dev *)ddi_get_driver_private(dip); dp->port = port; ddi_set_driver_private(dip, (caddr_t)dp); /* reset mii phy and start mii link watcher */ gem_mii_start(dp); DPRINTF(2, (CE_CONT, "!gem_do_attach: return: success")); return (dp); err_unregister: (void) mac_unregister(dp->mh); err_release_stats: /* release NDD resources */ gem_nd_cleanup(dp); err_free_ring: gem_free_memory(dp); err_free_regs: ddi_regs_map_free(&dp->regs_handle); err_free_locks: mutex_destroy(&dp->xmitlock); mutex_destroy(&dp->intrlock); cv_destroy(&dp->tx_drain_cv); err_free_private: if (macp) { mac_free(macp); } kmem_free((caddr_t)dp, GEM_LOCAL_DATA_SIZE(gc)); return (NULL); } int gem_do_detach(dev_info_t *dip) { struct gem_dev *dp; struct gem_dev *tmp; caddr_t private; int priv_size; ddi_acc_handle_t rh; dp = GEM_GET_DEV(dip); if (dp == NULL) { return (DDI_SUCCESS); } rh = dp->regs_handle; private = dp->private; priv_size = dp->priv_size; while (dp) { /* unregister with gld v3 */ if (mac_unregister(dp->mh) != 0) { return (DDI_FAILURE); } /* ensure any rx buffers are not used */ if (dp->rx_buf_allocated != dp->rx_buf_freecnt) { /* resource is busy */ cmn_err(CE_PANIC, "!%s: %s: rxbuf is busy: allocated:%d, freecnt:%d", dp->name, __func__, dp->rx_buf_allocated, dp->rx_buf_freecnt); /* NOT REACHED */ } /* stop mii link watcher */ gem_mii_stop(dp); /* unregister interrupt handler */ if (dp->misc_flag & GEM_SOFTINTR) { ddi_remove_softintr(dp->soft_id); } else if ((dp->misc_flag & GEM_NOINTR) == 0) { ddi_remove_intr(dip, 0, dp->iblock_cookie); } else { /* stop interrupt watcher */ if (dp->intr_watcher_id) { while (untimeout(dp->intr_watcher_id) == -1) ; dp->intr_watcher_id = 0; } } /* release NDD resources */ gem_nd_cleanup(dp); /* release buffers, descriptors and dma resources */ gem_free_memory(dp); /* release locks and condition variables */ mutex_destroy(&dp->xmitlock); mutex_destroy(&dp->intrlock); cv_destroy(&dp->tx_drain_cv); /* release basic memory resources */ tmp = dp->next; kmem_free((caddr_t)dp, GEM_LOCAL_DATA_SIZE(&dp->gc)); dp = tmp; } /* release common private memory for the nic */ kmem_free(private, priv_size); /* release register mapping resources */ ddi_regs_map_free(&rh); DPRINTF(2, (CE_CONT, "!%s%d: gem_do_detach: return: success", ddi_driver_name(dip), ddi_get_instance(dip))); return (DDI_SUCCESS); } int gem_suspend(dev_info_t *dip) { struct gem_dev *dp; /* * stop the device */ dp = GEM_GET_DEV(dip); ASSERT(dp); DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); for (; dp; dp = dp->next) { /* stop mii link watcher */ gem_mii_stop(dp); /* stop interrupt watcher for no-intr mode */ if (dp->misc_flag & GEM_NOINTR) { if (dp->intr_watcher_id) { while (untimeout(dp->intr_watcher_id) == -1) ; } dp->intr_watcher_id = 0; } /* stop tx timeout watcher */ if (dp->timeout_id) { while (untimeout(dp->timeout_id) == -1) ; dp->timeout_id = 0; } /* make the nic state inactive */ mutex_enter(&dp->intrlock); (void) gem_mac_stop(dp, 0); ASSERT(!dp->mac_active); /* no further register access */ dp->mac_suspended = B_TRUE; mutex_exit(&dp->intrlock); } /* XXX - power down the nic */ return (DDI_SUCCESS); } int gem_resume(dev_info_t *dip) { struct gem_dev *dp; /* * restart the device */ dp = GEM_GET_DEV(dip); ASSERT(dp); DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); for (; dp; dp = dp->next) { /* * Bring up the nic after power up */ /* gem_xxx.c layer to setup power management state. */ ASSERT(!dp->mac_active); /* reset the chip, because we are just after power up. */ mutex_enter(&dp->intrlock); dp->mac_suspended = B_FALSE; dp->nic_state = NIC_STATE_STOPPED; if ((*dp->gc.gc_reset_chip)(dp) != GEM_SUCCESS) { cmn_err(CE_WARN, "%s: %s: failed to reset chip", dp->name, __func__); mutex_exit(&dp->intrlock); goto err; } mutex_exit(&dp->intrlock); /* initialize mii phy because we are just after power up */ if (dp->gc.gc_mii_init) { (void) (*dp->gc.gc_mii_init)(dp); } if (dp->misc_flag & GEM_NOINTR) { /* * schedule first call of gem_intr_watcher * instead of interrupts. */ dp->intr_watcher_id = timeout((void (*)(void *))gem_intr_watcher, (void *)dp, drv_usectohz(3*1000000)); } /* restart mii link watcher */ gem_mii_start(dp); /* restart mac */ mutex_enter(&dp->intrlock); if (gem_mac_init(dp) != GEM_SUCCESS) { mutex_exit(&dp->intrlock); goto err_reset; } dp->nic_state = NIC_STATE_INITIALIZED; /* setup media mode if the link have been up */ if (dp->mii_state == MII_STATE_LINKUP) { if ((dp->gc.gc_set_media)(dp) != GEM_SUCCESS) { mutex_exit(&dp->intrlock); goto err_reset; } } /* enable mac address and rx filter */ dp->rxmode |= RXMODE_ENABLE; if ((*dp->gc.gc_set_rx_filter)(dp) != GEM_SUCCESS) { mutex_exit(&dp->intrlock); goto err_reset; } dp->nic_state = NIC_STATE_ONLINE; /* restart tx timeout watcher */ dp->timeout_id = timeout((void (*)(void *))gem_tx_timeout, (void *)dp, dp->gc.gc_tx_timeout_interval); /* now the nic is fully functional */ if (dp->mii_state == MII_STATE_LINKUP) { if (gem_mac_start(dp) != GEM_SUCCESS) { mutex_exit(&dp->intrlock); goto err_reset; } } mutex_exit(&dp->intrlock); } return (DDI_SUCCESS); err_reset: if (dp->intr_watcher_id) { while (untimeout(dp->intr_watcher_id) == -1) ; dp->intr_watcher_id = 0; } mutex_enter(&dp->intrlock); (*dp->gc.gc_reset_chip)(dp); dp->nic_state = NIC_STATE_STOPPED; mutex_exit(&dp->intrlock); err: return (DDI_FAILURE); } /* * misc routines for PCI */ uint8_t gem_search_pci_cap(dev_info_t *dip, ddi_acc_handle_t conf_handle, uint8_t target) { uint8_t pci_cap_ptr; uint32_t pci_cap; /* search power management capablities */ pci_cap_ptr = pci_config_get8(conf_handle, PCI_CONF_CAP_PTR); while (pci_cap_ptr) { /* read pci capability header */ pci_cap = pci_config_get32(conf_handle, pci_cap_ptr); if ((pci_cap & 0xff) == target) { /* found */ break; } /* get next_ptr */ pci_cap_ptr = (pci_cap >> 8) & 0xff; } return (pci_cap_ptr); } int gem_pci_set_power_state(dev_info_t *dip, ddi_acc_handle_t conf_handle, uint_t new_mode) { uint8_t pci_cap_ptr; uint32_t pmcsr; uint_t unit; const char *drv_name; ASSERT(new_mode < 4); unit = ddi_get_instance(dip); drv_name = ddi_driver_name(dip); /* search power management capablities */ pci_cap_ptr = gem_search_pci_cap(dip, conf_handle, PCI_CAP_ID_PM); if (pci_cap_ptr == 0) { cmn_err(CE_CONT, "!%s%d: doesn't have pci power management capability", drv_name, unit); return (DDI_FAILURE); } /* read power management capabilities */ pmcsr = pci_config_get32(conf_handle, pci_cap_ptr + PCI_PMCSR); DPRINTF(0, (CE_CONT, "!%s%d: pmc found at 0x%x: pmcsr: 0x%08x", drv_name, unit, pci_cap_ptr, pmcsr)); /* * Is the resuested power mode supported? */ /* not yet */ /* * move to new mode */ pmcsr = (pmcsr & ~PCI_PMCSR_STATE_MASK) | new_mode; pci_config_put32(conf_handle, pci_cap_ptr + PCI_PMCSR, pmcsr); return (DDI_SUCCESS); } /* * select suitable register for by specified address space or register * offset in PCI config space */ int gem_pci_regs_map_setup(dev_info_t *dip, uint32_t which, uint32_t mask, struct ddi_device_acc_attr *attrp, caddr_t *basep, ddi_acc_handle_t *hp) { struct pci_phys_spec *regs; uint_t len; uint_t unit; uint_t n; uint_t i; int ret; const char *drv_name; unit = ddi_get_instance(dip); drv_name = ddi_driver_name(dip); /* Search IO-range or memory-range to be mapped */ regs = NULL; len = 0; if ((ret = ddi_prop_lookup_int_array( DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "reg", (void *)®s, &len)) != DDI_PROP_SUCCESS) { cmn_err(CE_WARN, "!%s%d: failed to get reg property (ret:%d)", drv_name, unit, ret); return (DDI_FAILURE); } n = len / (sizeof (struct pci_phys_spec) / sizeof (int)); ASSERT(regs != NULL && len > 0); #if GEM_DEBUG_LEVEL > 0 for (i = 0; i < n; i++) { cmn_err(CE_CONT, "!%s%d: regs[%d]: %08x.%08x.%08x.%08x.%08x", drv_name, unit, i, regs[i].pci_phys_hi, regs[i].pci_phys_mid, regs[i].pci_phys_low, regs[i].pci_size_hi, regs[i].pci_size_low); } #endif for (i = 0; i < n; i++) { if ((regs[i].pci_phys_hi & mask) == which) { /* it's the requested space */ ddi_prop_free(regs); goto address_range_found; } } ddi_prop_free(regs); return (DDI_FAILURE); address_range_found: if ((ret = ddi_regs_map_setup(dip, i, basep, 0, 0, attrp, hp)) != DDI_SUCCESS) { cmn_err(CE_CONT, "!%s%d: ddi_regs_map_setup failed (ret:%d)", drv_name, unit, ret); } return (ret); } void gem_mod_init(struct dev_ops *dop, char *name) { mac_init_ops(dop, name); } void gem_mod_fini(struct dev_ops *dop) { mac_fini_ops(dop); }