| /* |
| * usbgem.c: General USB to Fast Ethernet mac driver framework |
| * |
| * Copyright (c) 2002-2012 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. |
| */ |
| |
| /* |
| * Change log |
| */ |
| |
| /* |
| * TODO: |
| * implement DELAYED_START |
| */ |
| |
| /* |
| * System Header files. |
| */ |
| #include <sys/types.h> |
| #include <sys/conf.h> |
| #include <sys/debug.h> |
| #include <sys/kmem.h> |
| #include <sys/vtrace.h> |
| #include <sys/ethernet.h> |
| #include <sys/modctl.h> |
| #include <sys/errno.h> |
| #include <sys/ddi.h> |
| #include <sys/sunddi.h> |
| #ifndef USBGEM_CONFIG_GLDv3 |
| #include <sys/dlpi.h> |
| #include <sys/strsubr.h> |
| #endif |
| #include <sys/stream.h> /* required for MBLK* */ |
| #include <sys/strsun.h> /* required for mionack() */ |
| #include <sys/byteorder.h> |
| |
| #include <sys/usb/usba.h> |
| #ifdef USBGEM_CONFIG_GLDv3 |
| #include <inet/common.h> |
| #include <inet/led.h> |
| #include <inet/mi.h> |
| #include <inet/nd.h> |
| #endif |
| |
| /* supplement definitions */ |
| extern const char *usb_str_cr(usb_cr_t); |
| |
| #ifndef USBGEM_CONFIG_GLDv3 |
| #pragma weak gld_linkstate |
| #endif |
| #include <sys/note.h> |
| |
| #include "usbgem_mii.h" |
| #include "usbgem.h" |
| |
| #ifdef MODULE |
| char ident[] = "usb general ethernet mac driver v" VERSION; |
| #else |
| extern char ident[]; |
| #endif |
| |
| /* Debugging support */ |
| #ifdef USBGEM_DEBUG_LEVEL |
| static int usbgem_debug = USBGEM_DEBUG_LEVEL; |
| #define DPRINTF(n, args) if (usbgem_debug > (n)) cmn_err args |
| #else |
| #define DPRINTF(n, args) |
| #endif |
| |
| /* |
| * Useful macros and typedefs |
| */ |
| #define ROUNDUP(x, a) (((x) + (a) - 1) & ~((a) - 1)) |
| #define DEFAULT_PIPE(dp) ((dp)->reg_data->dev_default_ph) |
| #define VTAG_SIZE 4 |
| #define BOOLEAN(x) ((x) != 0) |
| /* |
| * configuration parameters |
| */ |
| #define USBDRV_MAJOR_VER 2 |
| #define USBDRV_MINOR_VER 0 |
| |
| #define ETHERHEADERL (sizeof (struct ether_header)) |
| #define MAXPKTLEN(dp) ((dp)->mtu + ETHERHEADERL) |
| #define MAXPKTBUF(dp) ((dp)->mtu + ETHERHEADERL + ETHERFCSL) |
| |
| #define WATCH_INTERVAL_FAST drv_usectohz(100*1000) |
| |
| #define STOP_GRACEFUL B_TRUE |
| |
| /* |
| * Private functions |
| */ |
| static int usbgem_open_pipes(struct usbgem_dev *dp); |
| static int usbgem_close_pipes(struct usbgem_dev *dp); |
| static void usbgem_intr_cb(usb_pipe_handle_t, usb_intr_req_t *); |
| static void usbgem_bulkin_cb(usb_pipe_handle_t, usb_bulk_req_t *); |
| static void usbgem_bulkout_cb(usb_pipe_handle_t, usb_bulk_req_t *); |
| |
| static int usbgem_mii_start(struct usbgem_dev *); |
| static void usbgem_mii_stop(struct usbgem_dev *); |
| |
| /* local buffer management */ |
| static int usbgem_init_rx_buf(struct usbgem_dev *); |
| |
| /* internal mac interfaces */ |
| static void usbgem_tx_timeout(struct usbgem_dev *); |
| static void usbgem_mii_link_watcher(struct usbgem_dev *); |
| static int usbgem_mac_init(struct usbgem_dev *); |
| static int usbgem_mac_start(struct usbgem_dev *); |
| static int usbgem_mac_stop(struct usbgem_dev *, int, boolean_t); |
| static void usbgem_mac_ioctl(struct usbgem_dev *, queue_t *, mblk_t *); |
| |
| int usbgem_speed_value[] = {10, 100, 1000}; |
| |
| static int usbgem_ctrl_retry = 5; |
| |
| /* usb event support */ |
| static int usbgem_disconnect_cb(dev_info_t *dip); |
| static int usbgem_reconnect_cb(dev_info_t *dip); |
| int usbgem_suspend(dev_info_t *dip); |
| int usbgem_resume(dev_info_t *dip); |
| |
| static uint8_t usbgem_bcastaddr[] = { |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff |
| }; |
| |
| #ifdef MODULE |
| extern struct mod_ops mod_miscops; |
| |
| static struct modlmisc modlmisc = { |
| &mod_miscops, |
| "usbgem v" VERSION, |
| }; |
| |
| static struct modlinkage modlinkage = { |
| MODREV_1, &modlmisc, NULL |
| }; |
| |
| /* |
| * _init : done |
| */ |
| int |
| _init(void) |
| { |
| int status; |
| |
| DPRINTF(2, (CE_CONT, "!usbgem: _init: called")); |
| status = mod_install(&modlinkage); |
| |
| return (status); |
| } |
| |
| /* |
| * _fini : done |
| */ |
| int |
| _fini(void) |
| { |
| int status; |
| |
| DPRINTF(2, (CE_CONT, "!usbgem: _fini: called")); |
| status = mod_remove(&modlinkage); |
| return (status); |
| } |
| |
| int |
| _info(struct modinfo *modinfop) |
| { |
| return (mod_info(&modlinkage, modinfop)); |
| } |
| #endif /* MODULE */ |
| |
| /* ============================================================== */ |
| /* |
| * Ether CRC calculation utilities |
| */ |
| /* ============================================================== */ |
| /* |
| * Ether CRC calculation according to 21143 data sheet |
| */ |
| #define CRC32_POLY_LE 0xedb88320 |
| uint32_t |
| usbgem_ether_crc_le(const uint8_t *addr) |
| { |
| int idx; |
| int bit; |
| uint_t data; |
| uint32_t crc = 0xffffffff; |
| |
| crc = 0xffffffff; |
| for (idx = 0; idx < ETHERADDRL; idx++) { |
| for (data = *addr++, bit = 0; bit < 8; bit++, data >>= 1) { |
| crc = (crc >> 1) ^ |
| (((crc ^ data) & 1) ? CRC32_POLY_LE : 0); |
| } |
| } |
| return (crc); |
| } |
| |
| #define CRC32_POLY_BE 0x04c11db7 |
| uint32_t |
| usbgem_ether_crc_be(const uint8_t *addr) |
| { |
| int idx; |
| int bit; |
| uint_t data; |
| uint32_t crc; |
| |
| crc = 0xffffffff; |
| for (idx = 0; idx < ETHERADDRL; idx++) { |
| for (data = *addr++, bit = 0; bit < 8; bit++, data >>= 1) { |
| crc = (crc << 1) ^ |
| ((((crc >> 31) ^ data) & 1) ? CRC32_POLY_BE : 0); |
| } |
| } |
| return (crc); |
| } |
| |
| int |
| usbgem_prop_get_int(struct usbgem_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 |
| usbgem_population(uint32_t x) |
| { |
| int i; |
| int cnt; |
| |
| cnt = 0; |
| for (i = 0; i < 32; i++) { |
| if (x & (1 << i)) { |
| cnt++; |
| } |
| } |
| return (cnt); |
| } |
| |
| static clock_t |
| usbgem_timestamp_nz() |
| { |
| clock_t now; |
| now = ddi_get_lbolt(); |
| return (now ? now : (clock_t)1); |
| } |
| |
| #ifdef USBGEM_DEBUG_LEVEL |
| #ifdef USBGEM_DEBUG_VLAN |
| #ifdef notdef |
| #include <netinet/in.h> |
| #endif |
| static void |
| usbgem_dump_packet(struct usbgem_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 /* USBGEM_DEBUG_VLAN */ |
| #endif /* USBGEM_DEBUG_LEVEL */ |
| |
| #ifdef GEM_GCC_RUNTIME |
| /* |
| * gcc3 runtime routines |
| */ |
| #pragma weak memcmp |
| int |
| memcmp(const void *s1, const void *s2, size_t n) |
| { |
| int i; |
| int ret; |
| |
| ret = 0; |
| for (i = 0; i < n; i++) { |
| ret = (int)((uint8_t *)s1)[i] - (int)((uint8_t *)s2)[i]; |
| if (ret) { |
| return (ret); |
| } |
| } |
| return (0); |
| } |
| |
| #pragma weak memset |
| void * |
| memset(void *s, int c, size_t n) |
| { |
| if ((c & 0xff) == 0) { |
| bzero(s, n); |
| } else { |
| while (n--) { |
| ((uint8_t *)s)[n] = c; |
| } |
| } |
| return (s); |
| } |
| |
| #pragma weak _memcpy = memcpy |
| #pragma weak memcpy |
| void * |
| memcpy(void *s1, const void *s2, size_t n) |
| { |
| bcopy(s2, s1, n); |
| return (s1); |
| } |
| #endif /* GEM_GCC_RUNTIME */ |
| /* ============================================================== */ |
| /* |
| * hardware operations |
| */ |
| /* ============================================================== */ |
| static int |
| usbgem_hal_reset_chip(struct usbgem_dev *dp) |
| { |
| int err; |
| |
| sema_p(&dp->hal_op_lock); |
| err = (*dp->ugc.usbgc_reset_chip)(dp); |
| sema_v(&dp->hal_op_lock); |
| return (err); |
| } |
| |
| static int |
| usbgem_hal_init_chip(struct usbgem_dev *dp) |
| { |
| int err; |
| |
| sema_p(&dp->hal_op_lock); |
| err = (*dp->ugc.usbgc_init_chip)(dp); |
| sema_v(&dp->hal_op_lock); |
| return (err); |
| } |
| |
| static int |
| usbgem_hal_attach_chip(struct usbgem_dev *dp) |
| { |
| int err; |
| |
| sema_p(&dp->hal_op_lock); |
| err = (*dp->ugc.usbgc_attach_chip)(dp); |
| sema_v(&dp->hal_op_lock); |
| return (err); |
| } |
| |
| static int |
| usbgem_hal_set_rx_filter(struct usbgem_dev *dp) |
| { |
| int err; |
| |
| sema_p(&dp->hal_op_lock); |
| err = (*dp->ugc.usbgc_set_rx_filter)(dp); |
| sema_v(&dp->hal_op_lock); |
| return (err); |
| } |
| |
| static int |
| usbgem_hal_set_media(struct usbgem_dev *dp) |
| { |
| int err; |
| |
| sema_p(&dp->hal_op_lock); |
| err = (*dp->ugc.usbgc_set_media)(dp); |
| sema_v(&dp->hal_op_lock); |
| return (err); |
| } |
| |
| static int |
| usbgem_hal_start_chip(struct usbgem_dev *dp) |
| { |
| int err; |
| |
| sema_p(&dp->hal_op_lock); |
| err = (*dp->ugc.usbgc_start_chip)(dp); |
| sema_v(&dp->hal_op_lock); |
| return (err); |
| } |
| |
| static int |
| usbgem_hal_stop_chip(struct usbgem_dev *dp) |
| { |
| int err; |
| |
| sema_p(&dp->hal_op_lock); |
| err = (*dp->ugc.usbgc_stop_chip)(dp); |
| sema_v(&dp->hal_op_lock); |
| return (err); |
| } |
| |
| static int |
| usbgem_hal_get_stats(struct usbgem_dev *dp) |
| { |
| int err; |
| |
| sema_p(&dp->hal_op_lock); |
| err = (*dp->ugc.usbgc_get_stats)(dp); |
| sema_v(&dp->hal_op_lock); |
| return (err); |
| } |
| |
| |
| /* ============================================================== */ |
| /* |
| * USB pipe management |
| */ |
| /* ============================================================== */ |
| static boolean_t |
| usbgem_rx_start_unit(struct usbgem_dev *dp, usb_bulk_req_t *req) |
| { |
| mblk_t *mp; |
| int err; |
| usb_flags_t flags; |
| |
| ASSERT(req); |
| |
| mp = allocb(dp->rx_buf_len, BPRI_MED); |
| if (mp == NULL) { |
| cmn_err(CE_WARN, "!%s: %s: failed to allocate mblk", |
| dp->name, __func__); |
| goto err; |
| } |
| |
| req->bulk_len = dp->rx_buf_len; |
| req->bulk_data = mp; |
| req->bulk_client_private = (usb_opaque_t)dp; |
| req->bulk_timeout = 0; |
| req->bulk_attributes = USB_ATTRS_SHORT_XFER_OK; |
| req->bulk_cb = usbgem_bulkin_cb; |
| req->bulk_exc_cb = usbgem_bulkin_cb; |
| req->bulk_completion_reason = 0; |
| req->bulk_cb_flags = 0; |
| |
| flags = 0; |
| err = usb_pipe_bulk_xfer(dp->bulkin_pipe, req, flags); |
| |
| if (err != USB_SUCCESS) { |
| cmn_err(CE_WARN, "%s: failed to bulk_xfer for rx, err:%d", |
| dp->name, err); |
| |
| /* free req and mp */ |
| usb_free_bulk_req(req); |
| goto err; |
| } |
| return (B_TRUE); |
| err: |
| return (B_FALSE); |
| } |
| |
| /* ============================================================== */ |
| /* |
| * Rx/Tx buffer management |
| */ |
| /* ============================================================== */ |
| static int |
| usbgem_init_rx_buf(struct usbgem_dev *dp) |
| { |
| int i; |
| usb_bulk_req_t *req; |
| |
| ASSERT(dp->mac_state == MAC_STATE_ONLINE); |
| |
| for (i = 0; i < dp->ugc.usbgc_rx_list_max; i++) { |
| req = usb_alloc_bulk_req(dp->dip, 0, USB_FLAGS_SLEEP); |
| if (req == NULL) { |
| cmn_err(CE_WARN, |
| "!%s: %s: failed to allocate bulkreq for rx", |
| dp->name, __func__); |
| return (USB_FAILURE); |
| } |
| if (!usbgem_rx_start_unit(dp, req)) { |
| return (USB_FAILURE); |
| } |
| mutex_enter(&dp->rxlock); |
| dp->rx_busy_cnt++; |
| mutex_exit(&dp->rxlock); |
| } |
| return (USB_SUCCESS); |
| } |
| |
| /* ============================================================== */ |
| /* |
| * memory resource management |
| */ |
| /* ============================================================== */ |
| static int |
| usbgem_free_memory(struct usbgem_dev *dp) |
| { |
| usb_bulk_req_t *req; |
| |
| /* free all tx requst structure */ |
| while ((req = dp->tx_free_list) != NULL) { |
| dp->tx_free_list = |
| (usb_bulk_req_t *)req->bulk_client_private; |
| req->bulk_data = NULL; |
| usb_free_bulk_req(req); |
| } |
| return (USB_SUCCESS); |
| } |
| |
| static int |
| usbgem_alloc_memory(struct usbgem_dev *dp) |
| { |
| int i; |
| usb_bulk_req_t *req; |
| |
| /* allocate tx requests */ |
| dp->tx_free_list = NULL; |
| for (i = 0; i < dp->ugc.usbgc_tx_list_max; i++) { |
| req = usb_alloc_bulk_req(dp->dip, 0, USB_FLAGS_SLEEP); |
| if (req == NULL) { |
| cmn_err(CE_WARN, |
| "%s:%s failed to allocate tx requests", |
| dp->name, __func__); |
| |
| /* free partially allocated tx requests */ |
| (void) usbgem_free_memory(dp); |
| return (USB_FAILURE); |
| } |
| |
| /* add the new one allocated into tx free list */ |
| req->bulk_client_private = (usb_opaque_t)dp->tx_free_list; |
| dp->tx_free_list = req; |
| } |
| |
| return (USB_SUCCESS); |
| } |
| |
| /* ========================================================== */ |
| /* |
| * Start transmission. |
| * Return zero on success, |
| */ |
| /* ========================================================== */ |
| |
| #ifdef TXTIMEOUT_TEST |
| static int usbgem_send_cnt = 0; |
| #endif |
| |
| /* |
| * usbgem_send is used only to send data packet into ethernet line. |
| */ |
| static mblk_t * |
| usbgem_send_common(struct usbgem_dev *dp, mblk_t *mp, uint32_t flags) |
| { |
| int err; |
| mblk_t *new; |
| usb_bulk_req_t *req; |
| int mcast; |
| int bcast; |
| int len; |
| boolean_t intr; |
| usb_flags_t usb_flags = 0; |
| #ifdef USBGEM_DEBUG_LEVEL |
| usb_pipe_state_t p_state; |
| #endif |
| DPRINTF(2, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| |
| intr = (flags & 1) != 0; |
| len = msgdsize(mp); |
| bcast = 0; |
| mcast = 0; |
| if (mp->b_rptr[0] & 1) { |
| if (bcmp(mp->b_rptr, &usbgem_bcastaddr, ETHERADDRL) == 0) { |
| bcast = 1; |
| } else { |
| mcast = 1; |
| } |
| } |
| new = (*dp->ugc.usbgc_tx_make_packet)(dp, mp); |
| if (new == NULL) { |
| /* |
| * no memory resource. we don't stop downstream, |
| * we just discard the packet. |
| */ |
| DPRINTF(0, (CE_CONT, "!%s: %s: no memory", |
| dp->name, __func__)); |
| freemsg(mp); |
| |
| mutex_enter(&dp->txlock); |
| dp->stats.noxmtbuf++; |
| dp->stats.errxmt++; |
| mutex_exit(&dp->txlock); |
| |
| return (NULL); |
| } |
| |
| ASSERT(new->b_cont == NULL); |
| |
| mutex_enter(&dp->txlock); |
| if (dp->tx_free_list == NULL) { |
| /* |
| * no tx free slot |
| */ |
| ASSERT(dp->tx_busy_cnt == dp->ugc.usbgc_tx_list_max); |
| mutex_exit(&dp->txlock); |
| |
| DPRINTF(4, (CE_CONT, "!%s: %s: no free slot", |
| dp->name, __func__)); |
| if (new && new != mp) { |
| /* free reallocated message */ |
| freemsg(new); |
| } |
| return (mp); |
| } |
| req = dp->tx_free_list; |
| dp->tx_free_list = (usb_bulk_req_t *)req->bulk_client_private; |
| dp->tx_busy_cnt++; |
| |
| if (dp->tx_free_list == NULL) { |
| intr = B_TRUE; |
| } |
| if (intr) { |
| dp->tx_intr_pended++; |
| } |
| DB_TCI(new) = intr; |
| #ifdef USBGEM_DEBUG_LEVEL |
| new->b_datap->db_cksum32 = dp->tx_seq_num; |
| dp->tx_seq_num++; |
| #endif |
| dp->stats.obytes += len; |
| dp->stats.opackets++; |
| if (bcast | mcast) { |
| dp->stats.obcast += bcast; |
| dp->stats.omcast += mcast; |
| } |
| mutex_exit(&dp->txlock); |
| |
| DPRINTF(2, (CE_CONT, "!%s: %s: sending", dp->name, __func__)); |
| |
| req->bulk_len = (long)new->b_wptr - (long)new->b_rptr; |
| req->bulk_data = new; |
| req->bulk_client_private = (usb_opaque_t)dp; |
| req->bulk_timeout = dp->bulkout_timeout; /* in second */ |
| req->bulk_attributes = 0; |
| req->bulk_cb = usbgem_bulkout_cb; |
| req->bulk_exc_cb = usbgem_bulkout_cb; |
| req->bulk_completion_reason = 0; |
| req->bulk_cb_flags = 0; |
| |
| if (intr) { |
| usb_flags = USB_FLAGS_SLEEP; |
| } |
| if ((err = usb_pipe_bulk_xfer(dp->bulkout_pipe, req, usb_flags)) |
| != USB_SUCCESS) { |
| |
| /* failed to transfer the packet, discard it. */ |
| freemsg(new); |
| req->bulk_data = NULL; |
| |
| /* recycle the request block */ |
| mutex_enter(&dp->txlock); |
| dp->tx_busy_cnt--; |
| req->bulk_client_private = (usb_opaque_t)dp->tx_free_list; |
| dp->tx_free_list = req; |
| mutex_exit(&dp->txlock); |
| |
| cmn_err(CE_NOTE, |
| "%s: %s: usb_pipe_bulk_xfer: failed: err:%d", |
| dp->name, __func__, err); |
| |
| /* we use another flag to indicate error state. */ |
| if (dp->fatal_error == (clock_t)0) { |
| dp->fatal_error = usbgem_timestamp_nz(); |
| } |
| } else { |
| /* record the start time */ |
| dp->tx_start_time = ddi_get_lbolt(); |
| } |
| |
| if (err == USB_SUCCESS && (usb_flags & USB_FLAGS_SLEEP)) { |
| usbgem_bulkout_cb(dp->bulkout_pipe, req); |
| } |
| |
| if (new != mp) { |
| freemsg(mp); |
| } |
| return (NULL); |
| } |
| |
| int |
| usbgem_restart_nic(struct usbgem_dev *dp) |
| { |
| int ret; |
| int flags = 0; |
| |
| DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| |
| ASSERT(dp->mac_state != MAC_STATE_DISCONNECTED); |
| |
| /* |
| * ensure to stop the nic |
| */ |
| if (dp->mac_state == MAC_STATE_ONLINE) { |
| (void) usbgem_mac_stop(dp, MAC_STATE_STOPPED, STOP_GRACEFUL); |
| } |
| |
| /* now the nic become quiescent, reset the chip */ |
| if (usbgem_hal_reset_chip(dp) != USB_SUCCESS) { |
| cmn_err(CE_WARN, "%s: %s: failed to reset chip", |
| dp->name, __func__); |
| goto err; |
| } |
| |
| /* |
| * restore the nic state step by step |
| */ |
| if (dp->nic_state < NIC_STATE_INITIALIZED) { |
| goto done; |
| } |
| |
| if (usbgem_mac_init(dp) != USB_SUCCESS) { |
| cmn_err(CE_WARN, "%s: %s: failed to initialize chip", |
| dp->name, __func__); |
| goto err; |
| } |
| |
| /* setup mac address and enable rx filter */ |
| sema_p(&dp->rxfilter_lock); |
| dp->rxmode |= RXMODE_ENABLE; |
| ret = usbgem_hal_set_rx_filter(dp); |
| sema_v(&dp->rxfilter_lock); |
| if (ret != USB_SUCCESS) { |
| goto err; |
| } |
| |
| /* |
| * update the link state asynchronously |
| */ |
| cv_signal(&dp->link_watcher_wait_cv); |
| |
| /* |
| * 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) { |
| if (usbgem_hal_set_media(dp) != USB_SUCCESS) { |
| goto err; |
| } |
| if (dp->nic_state < NIC_STATE_ONLINE) { |
| goto done; |
| } |
| |
| (void) usbgem_mac_start(dp); |
| |
| } |
| done: |
| return (USB_SUCCESS); |
| err: |
| #ifdef GEM_CONFIG_FMA |
| ddi_fm_service_impact(dp->dip, DDI_SERVICE_DEGRADED); |
| #endif |
| return (USB_FAILURE); |
| } |
| |
| static void |
| usbgem_tx_timeout(struct usbgem_dev *dp) |
| { |
| uint_t rwlock; |
| clock_t now; |
| |
| for (; ; ) { |
| mutex_enter(&dp->tx_watcher_lock); |
| (void) cv_timedwait(&dp->tx_watcher_cv, &dp->tx_watcher_lock, |
| dp->tx_watcher_interval + ddi_get_lbolt()); |
| mutex_exit(&dp->tx_watcher_lock); |
| |
| if (dp->tx_watcher_stop) { |
| break; |
| } |
| |
| now = ddi_get_lbolt(); |
| |
| rwlock = RW_READER; |
| again: |
| rw_enter(&dp->dev_state_lock, rwlock); |
| |
| if ((dp->mac_state != MAC_STATE_DISCONNECTED && |
| dp->fatal_error && |
| now - dp->fatal_error >= dp->ugc.usbgc_tx_timeout) || |
| (dp->mac_state == MAC_STATE_ONLINE && |
| dp->mii_state == MII_STATE_LINKUP && |
| dp->tx_busy_cnt != 0 && |
| now - dp->tx_start_time >= dp->ugc.usbgc_tx_timeout)) { |
| if (rwlock == RW_READER) { |
| /* |
| * Upgrade dev_state_lock from shared mode |
| * to exclusive mode to restart nic |
| */ |
| rwlock = RW_WRITER; |
| rw_exit(&dp->dev_state_lock); |
| goto again; |
| } |
| cmn_err(CE_WARN, "%s: %s: restarting the nic:" |
| " fatal_error:%ld nic_state:%d" |
| " mac_state:%d starttime:%ld", |
| dp->name, __func__, |
| dp->fatal_error ? now - dp->fatal_error: 0, |
| dp->nic_state, dp->mac_state, |
| dp->tx_busy_cnt ? now - dp->tx_start_time : 0); |
| |
| (void) usbgem_restart_nic(dp); |
| } |
| |
| rw_exit(&dp->dev_state_lock); |
| } |
| } |
| |
| static int |
| usbgem_tx_watcher_start(struct usbgem_dev *dp) |
| { |
| int err; |
| kthread_t *wdth; |
| |
| DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| |
| /* make a first call of uwgem_lw_link_check() */ |
| dp->tx_watcher_stop = 0; |
| dp->tx_watcher_interval = drv_usectohz(1000*1000); |
| |
| wdth = thread_create(NULL, 0, usbgem_tx_timeout, dp, 0, &p0, |
| TS_RUN, minclsyspri); |
| if (wdth == NULL) { |
| cmn_err(CE_WARN, |
| "!%s: %s: failed to create a tx_watcher thread", |
| dp->name, __func__); |
| return (USB_FAILURE); |
| } |
| dp->tx_watcher_did = wdth->t_did; |
| |
| return (USB_SUCCESS); |
| } |
| |
| static void |
| usbgem_tx_watcher_stop(struct usbgem_dev *dp) |
| { |
| DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| if (dp->tx_watcher_did) { |
| /* Ensure timer routine stopped */ |
| dp->tx_watcher_stop = 1; |
| cv_signal(&dp->tx_watcher_cv); |
| thread_join(dp->tx_watcher_did); |
| dp->tx_watcher_did = NULL; |
| } |
| } |
| |
| /* ================================================================== */ |
| /* |
| * Callback handlers |
| */ |
| /* ================================================================== */ |
| static void |
| usbgem_bulkin_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req) |
| { |
| mblk_t *newmp; |
| mblk_t *mp; |
| mblk_t *tp; |
| uint64_t len = 0; |
| int pkts = 0; |
| int bcast = 0; |
| int mcast = 0; |
| boolean_t busy; |
| struct usbgem_dev *dp; |
| |
| dp = (struct usbgem_dev *)req->bulk_client_private; |
| mp = req->bulk_data; |
| req->bulk_data = NULL; |
| |
| DPRINTF(2, (CE_CONT, "!%s: %s: mp:%p, cr:%s(%d)", |
| dp->name, __func__, mp, |
| usb_str_cr(req->bulk_completion_reason), |
| req->bulk_completion_reason)); |
| |
| /* |
| * we cannot acquire dev_state_lock because the routine |
| * must be executed during usbgem_mac_stop() to avoid |
| * dead lock. |
| * we use a simle membar operation to get the state correctly. |
| */ |
| membar_consumer(); |
| |
| if (req->bulk_completion_reason == USB_CR_OK && |
| dp->nic_state == NIC_STATE_ONLINE) { |
| newmp = (*dp->ugc.usbgc_rx_make_packet)(dp, mp); |
| |
| if (newmp != mp) { |
| /* the message has been reallocated, free old one */ |
| freemsg(mp); |
| } |
| |
| /* the message may includes one or more ethernet packets */ |
| for (tp = newmp; tp; tp = tp->b_next) { |
| len += (uintptr_t)tp->b_wptr - (uintptr_t)tp->b_rptr; |
| pkts++; |
| if (tp->b_rptr[0] & 1) { |
| if (bcmp(tp->b_rptr, &usbgem_bcastaddr, |
| ETHERADDRL) == 0) { |
| bcast++; |
| } else { |
| mcast++; |
| } |
| } |
| } |
| |
| /* send up if it is a valid packet */ |
| #ifdef USBGEM_CONFIG_GLDv3 |
| mac_rx(dp->mh, NULL, newmp); |
| #else |
| while (newmp) { |
| tp = newmp; |
| newmp = newmp->b_next; |
| tp->b_next = NULL; |
| gld_recv(dp->macinfo, tp); |
| } |
| #endif |
| } else { |
| freemsg(mp); |
| len = 0; |
| } |
| |
| mutex_enter(&dp->rxlock); |
| /* update rx_active */ |
| if (dp->rx_active) { |
| dp->rx_active = dp->mac_state == MAC_STATE_ONLINE; |
| } |
| |
| dp->stats.rbytes += len; |
| dp->stats.rpackets += pkts; |
| if (bcast | mcast) { |
| dp->stats.rbcast += bcast; |
| dp->stats.rmcast += mcast; |
| } |
| mutex_exit(&dp->rxlock); |
| |
| if (dp->rx_active) { |
| /* prepare to receive the next packets */ |
| if (usbgem_rx_start_unit(dp, req)) { |
| /* we successed */ |
| goto done; |
| } |
| cmn_err(CE_WARN, |
| "!%s: %s: failed to fill next rx packet", |
| dp->name, __func__); |
| /* |
| * we use another flag to indicate error state. |
| * if we acquire dev_state_lock for RW_WRITER here, |
| * usbgem_mac_stop() may hang. |
| */ |
| if (dp->fatal_error == (clock_t)0) { |
| dp->fatal_error = usbgem_timestamp_nz(); |
| } |
| } else { |
| /* no need to prepare the next packets */ |
| usb_free_bulk_req(req); |
| } |
| |
| mutex_enter(&dp->rxlock); |
| dp->rx_active = B_FALSE; |
| dp->rx_busy_cnt--; |
| if (dp->rx_busy_cnt == 0) { |
| /* wake up someone waits for me */ |
| cv_broadcast(&dp->rx_drain_cv); |
| } |
| mutex_exit(&dp->rxlock); |
| done: |
| ; |
| } |
| |
| static void |
| usbgem_bulkout_cb(usb_pipe_handle_t pipe, usb_bulk_req_t *req) |
| { |
| boolean_t intr; |
| boolean_t tx_sched; |
| struct usbgem_dev *dp; |
| |
| dp = (struct usbgem_dev *)req->bulk_client_private; |
| tx_sched = B_FALSE; |
| |
| DPRINTF(2, (CE_CONT, |
| "!%s: %s: cr:%s(%d) cb_flags:0x%x head:%d tail:%d", |
| dp->name, __func__, |
| usb_str_cr(req->bulk_completion_reason), |
| req->bulk_completion_reason, |
| req->bulk_cb_flags, |
| dp->tx_busy_cnt)); |
| |
| /* we have finished to transfer the packet into tx fifo */ |
| intr = DB_TCI(req->bulk_data); |
| freemsg(req->bulk_data); |
| |
| if (req->bulk_completion_reason != USB_CR_OK && |
| dp->fatal_error == (clock_t)0) { |
| dp->fatal_error = usbgem_timestamp_nz(); |
| } |
| |
| mutex_enter(&dp->txlock); |
| |
| if (intr) { |
| ASSERT(dp->tx_intr_pended > 0); |
| /* find the last interrupt we have scheduled */ |
| if (--(dp->tx_intr_pended) == 0) { |
| tx_sched = B_TRUE; |
| } |
| } |
| |
| ASSERT(dp->tx_busy_cnt > 0); |
| req->bulk_client_private = (usb_opaque_t)dp->tx_free_list; |
| dp->tx_free_list = req; |
| dp->tx_busy_cnt--; |
| |
| #ifdef CONFIG_TX_LIMITER |
| if (tx_sched) { |
| dp->tx_max_packets = |
| min(dp->tx_max_packets + 1, dp->ugc.usbgc_tx_list_max); |
| } |
| #endif |
| if (dp->mac_state != MAC_STATE_ONLINE && dp->tx_busy_cnt == 0) { |
| cv_broadcast(&dp->tx_drain_cv); |
| } |
| |
| mutex_exit(&dp->txlock); |
| |
| if (tx_sched) { |
| #ifdef USBGEM_CONFIG_GLDv3 |
| mac_tx_update(dp->mh); |
| #else |
| gld_sched(dp->macinfo); |
| #endif |
| } |
| } |
| |
| static void |
| usbgem_intr_cb(usb_pipe_handle_t ph, usb_intr_req_t *req) |
| { |
| struct usbgem_dev *dp; |
| |
| dp = (struct usbgem_dev *)req->intr_client_private; |
| dp->stats.intr++; |
| |
| if (req->intr_completion_reason == USB_CR_OK) { |
| (*dp->ugc.usbgc_interrupt)(dp, req->intr_data); |
| } |
| |
| /* free the request and data */ |
| usb_free_intr_req(req); |
| } |
| |
| /* ======================================================================== */ |
| /* |
| * MII support routines |
| */ |
| /* ======================================================================== */ |
| static void |
| usbgem_choose_forcedmode(struct usbgem_dev *dp) |
| { |
| /* choose media mode */ |
| if (dp->anadv_1000fdx || dp->anadv_1000hdx) { |
| dp->speed = USBGEM_SPD_1000; |
| dp->full_duplex = dp->anadv_1000fdx; |
| } else if (dp->anadv_100fdx || dp->anadv_100t4) { |
| dp->speed = USBGEM_SPD_100; |
| dp->full_duplex = B_TRUE; |
| } else if (dp->anadv_100hdx) { |
| dp->speed = USBGEM_SPD_100; |
| dp->full_duplex = B_FALSE; |
| } else { |
| dp->speed = USBGEM_SPD_10; |
| dp->full_duplex = dp->anadv_10fdx; |
| } |
| } |
| |
| static uint16_t |
| usbgem_mii_read(struct usbgem_dev *dp, uint_t reg, int *errp) |
| { |
| uint16_t val; |
| |
| sema_p(&dp->hal_op_lock); |
| val = (*dp->ugc.usbgc_mii_read)(dp, reg, errp); |
| sema_v(&dp->hal_op_lock); |
| |
| return (val); |
| } |
| |
| static void |
| usbgem_mii_write(struct usbgem_dev *dp, uint_t reg, uint16_t val, int *errp) |
| { |
| sema_p(&dp->hal_op_lock); |
| (*dp->ugc.usbgc_mii_write)(dp, reg, val, errp); |
| sema_v(&dp->hal_op_lock); |
| } |
| |
| static int |
| usbgem_mii_probe(struct usbgem_dev *dp) |
| { |
| int err; |
| |
| err = (*dp->ugc.usbgc_mii_probe)(dp); |
| return (err); |
| } |
| |
| static int |
| usbgem_mii_init(struct usbgem_dev *dp) |
| { |
| int err; |
| |
| err = (*dp->ugc.usbgc_mii_init)(dp); |
| return (err); |
| } |
| |
| #define fc_cap_decode(x) \ |
| ((((x) & MII_ABILITY_PAUSE) != 0 ? 1 : 0) | \ |
| (((x) & MII_ABILITY_ASM_DIR) != 0 ? 2 : 0)) |
| |
| int |
| usbgem_mii_config_default(struct usbgem_dev *dp, int *errp) |
| { |
| uint16_t mii_stat; |
| uint16_t val; |
| |
| 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 (USB_FAILURE); |
| } |
| |
| /* Do not change the rest of ability bits in advert reg */ |
| val = usbgem_mii_read(dp, MII_AN_ADVERT, errp) & ~MII_ABILITY_ALL; |
| if (*errp != USB_SUCCESS) { |
| goto usberr; |
| } |
| |
| 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)); |
| |
| /* set technology bits */ |
| 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 capabilities */ |
| if (dp->anadv_pause) { |
| val |= MII_ABILITY_PAUSE; |
| } |
| if (dp->anadv_asmpause) { |
| val |= MII_ABILITY_ASM_DIR; |
| } |
| |
| DPRINTF(0, (CE_CONT, |
| "!%s: %s: setting MII_AN_ADVERT reg:%b, pause:%d, asmpause:%d", |
| dp->name, __func__, val, MII_ABILITY_BITS, |
| dp->anadv_pause, dp->anadv_asmpause)); |
| |
| usbgem_mii_write(dp, MII_AN_ADVERT, val, errp); |
| if (*errp != USB_SUCCESS) { |
| goto usberr; |
| } |
| |
| if (dp->mii_status & MII_STATUS_XSTATUS) { |
| /* |
| * 1000Base-T GMII support |
| */ |
| if (!dp->anadv_autoneg) { |
| /* enable manual configuration */ |
| val = MII_1000TC_CFG_EN; |
| if (dp->anadv_1000t_ms == 2) { |
| val |= MII_1000TC_CFG_VAL; |
| } |
| } else { |
| val = 0; |
| if (dp->anadv_1000fdx) { |
| val |= MII_1000TC_ADV_FULL; |
| } |
| if (dp->anadv_1000hdx) { |
| val |= MII_1000TC_ADV_HALF; |
| } |
| switch (dp->anadv_1000t_ms) { |
| case 1: |
| /* slave */ |
| val |= MII_1000TC_CFG_EN; |
| break; |
| |
| case 2: |
| /* master */ |
| val |= MII_1000TC_CFG_EN | MII_1000TC_CFG_VAL; |
| break; |
| |
| default: |
| /* auto: do nothing */ |
| break; |
| } |
| } |
| DPRINTF(0, (CE_CONT, |
| "!%s: %s: setting MII_1000TC reg:%b", |
| dp->name, __func__, val, MII_1000TC_BITS)); |
| |
| usbgem_mii_write(dp, MII_1000TC, val, errp); |
| if (*errp != USB_SUCCESS) { |
| goto usberr; |
| } |
| } |
| return (USB_SUCCESS); |
| |
| usberr: |
| return (*errp); |
| } |
| |
| static char *usbgem_fc_type[] = { |
| "without", |
| "with symmetric", |
| "with tx", |
| "with rx", |
| }; |
| |
| #ifdef USBGEM_CONFIG_GLDv3 |
| #define USBGEM_LINKUP(dp) mac_link_update((dp)->mh, LINK_STATE_UP) |
| #define USBGEM_LINKDOWN(dp) mac_link_update((dp)->mh, LINK_STATE_DOWN) |
| #else |
| #define USBGEM_LINKUP(dp) \ |
| if (gld_linkstate) { \ |
| gld_linkstate((dp)->macinfo, GLD_LINKSTATE_UP); \ |
| } |
| #define USBGEM_LINKDOWN(dp) \ |
| if (gld_linkstate) { \ |
| gld_linkstate((dp)->macinfo, GLD_LINKSTATE_DOWN); \ |
| } |
| #endif |
| |
| static uint8_t usbgem_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 boolean_t |
| usbgem_mii_link_check(struct usbgem_dev *dp, int *oldstatep, int *newstatep) |
| { |
| 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; |
| int err; |
| uint_t rwlock; |
| |
| DPRINTF(4, (CE_CONT, "!%s: %s: time:%d state:%d", |
| dp->name, __func__, ddi_get_lbolt(), dp->mii_state)); |
| |
| if (dp->mii_state != MII_STATE_LINKUP) { |
| rwlock = RW_WRITER; |
| } else { |
| rwlock = RW_READER; |
| } |
| again: |
| rw_enter(&dp->dev_state_lock, rwlock); |
| |
| /* save old mii state */ |
| *oldstatep = dp->mii_state; |
| |
| if (dp->mac_state == MAC_STATE_DISCONNECTED) { |
| /* stop periodic execution of the link watcher */ |
| dp->mii_interval = 0; |
| tx_sched = B_FALSE; |
| goto next; |
| } |
| |
| now = ddi_get_lbolt(); |
| diff = now - dp->mii_last_check; |
| dp->mii_last_check = now; |
| |
| /* |
| * For NWAM, don't show linkdown state right |
| * when the device is attached. |
| */ |
| 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: |
| 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; |
| } |
| |
| val = usbgem_mii_read(dp, MII_CONTROL, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| 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 */ |
| usbgem_mii_write(dp, MII_CONTROL, 0, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| #if USBGEM_DEBUG_LEVEL > 10 |
| val = usbgem_mii_read(dp, MII_CONTROL, &err); |
| cmn_err(CE_CONT, "!%s: readback control %b", |
| dp->name, val, MII_CONTROL_BITS); |
| #endif |
| /* As resetting PHY has completed, configure PHY registers */ |
| if ((*dp->ugc.usbgc_mii_config)(dp, &err) != USB_SUCCESS) { |
| /* we failed to configure PHY */ |
| goto usberr; |
| } |
| |
| /* prepare for forced mode */ |
| usbgem_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 = dp->ugc.usbgc_mii_linkdown_timeout; |
| goto next_nowait; |
| } |
| |
| /* issue an auto-negotiation command */ |
| goto autonego; |
| |
| case MII_STATE_AUTONEGOTIATING: |
| /* |
| * Autonegotiation in progress |
| */ |
| dp->mii_timer -= diff; |
| if (dp->mii_timer - |
| (dp->ugc.usbgc_mii_an_timeout - dp->ugc.usbgc_mii_an_wait) |
| > 0) { |
| /* wait for minimum time (2.3 - 2.5 sec) */ |
| dp->mii_interval = WATCH_INTERVAL_FAST; |
| goto next; |
| } |
| |
| /* read PHY status */ |
| status = usbgem_mii_read(dp, MII_STATUS, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| 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 has been timed out, |
| * Reset PHY and try again. |
| */ |
| 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 for a while. |
| */ |
| dp->mii_interval = dp->ugc.usbgc_mii_an_watch_interval; |
| goto next; |
| } |
| |
| /* |
| * Auto-negotiation has been completed. Let's go to AN_DONE. |
| */ |
| dp->mii_state = MII_STATE_AN_DONE; |
| dp->mii_supress_msg = B_FALSE; |
| DPRINTF(0, (CE_CONT, |
| "!%s: auto-negotiation completed, MII_STATUS:%b", |
| dp->name, status, MII_STATUS_BITS)); |
| |
| if (dp->ugc.usbgc_mii_an_delay > 0) { |
| dp->mii_timer = dp->ugc.usbgc_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 has 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; |
| } |
| |
| /* |
| * Setup speed and duplex mode according with |
| * the result of auto negotiation. |
| */ |
| |
| /* |
| * Read registers required to determin current |
| * duplex mode and media speed. |
| */ |
| if (dp->ugc.usbgc_mii_an_delay > 0) { |
| /* the 'status' variable is not initialized yet */ |
| status = usbgem_mii_read(dp, MII_STATUS, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| } |
| advert = usbgem_mii_read(dp, MII_AN_ADVERT, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| lpable = usbgem_mii_read(dp, MII_AN_LPABLE, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| exp = usbgem_mii_read(dp, MII_AN_EXPANSION, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| if (exp == 0xffff) { |
| /* some phys don't have exp register */ |
| exp = 0; |
| } |
| |
| ctl1000 = 0; |
| stat1000 = 0; |
| if (dp->mii_status & MII_STATUS_XSTATUS) { |
| ctl1000 = usbgem_mii_read(dp, MII_1000TC, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| stat1000 = usbgem_mii_read(dp, MII_1000TS, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| } |
| 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: " |
| "status:%b, advert:%b, lpable:%b, exp:%b", |
| dp->name, |
| status, MII_STATUS_BITS, |
| advert, MII_ABILITY_BITS, |
| lpable, MII_ABILITY_BITS, |
| exp, MII_AN_EXP_BITS); |
| |
| DPRINTF(0, (CE_CONT, "!%s: MII_STATUS:%b", |
| dp->name, status, MII_STATUS_BITS)); |
| |
| if (dp->mii_status & MII_STATUS_XSTATUS) { |
| cmn_err(CE_CONT, |
| "! MII_1000TC reg:%b, MII_1000TS reg:%b", |
| ctl1000, MII_1000TC_BITS, |
| stat1000, MII_1000TS_BITS); |
| } |
| |
| if (usbgem_population(lpable) <= 1 && |
| (exp & MII_AN_EXP_LPCANAN) == 0) { |
| if ((advert & MII_ABILITY_TECH) != lpable) { |
| cmn_err(CE_WARN, |
| "!%s: but the link partner doesn't seem" |
| " to have auto-negotiation capability." |
| " please check the link configuration.", |
| dp->name); |
| } |
| /* |
| * it should be a result of pararell detection, |
| * which cannot detect duplex mode. |
| */ |
| if ((advert & lpable) == 0 && |
| lpable & MII_ABILITY_10BASE_T) { |
| /* no common technology, try 10M half mode */ |
| lpable |= advert & MII_ABILITY_10BASE_T; |
| 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 = USBGEM_SPD_1000; |
| dp->full_duplex = B_TRUE; |
| } else if ((ctl1000 & MII_1000TC_ADV_HALF) && |
| (stat1000 & MII_1000TS_LP_HALF)) { |
| /* 1000BaseT & half duplex */ |
| dp->speed = USBGEM_SPD_1000; |
| dp->full_duplex = B_FALSE; |
| } else if ((val & MII_ABILITY_100BASE_TX_FD)) { |
| /* 100BaseTx & fullduplex */ |
| dp->speed = USBGEM_SPD_100; |
| dp->full_duplex = B_TRUE; |
| } else if ((val & MII_ABILITY_100BASE_T4)) { |
| /* 100BaseTx & fullduplex */ |
| dp->speed = USBGEM_SPD_100; |
| dp->full_duplex = B_TRUE; |
| } else if ((val & MII_ABILITY_100BASE_TX)) { |
| /* 100BaseTx & half duplex */ |
| dp->speed = USBGEM_SPD_100; |
| dp->full_duplex = B_FALSE; |
| } else if ((val & MII_ABILITY_10BASE_T_FD)) { |
| /* 10BaseT & full duplex */ |
| dp->speed = USBGEM_SPD_10; |
| dp->full_duplex = B_TRUE; |
| } else if ((val & MII_ABILITY_10BASE_T)) { |
| /* 10BaseT & half duplex */ |
| dp->speed = USBGEM_SPD_10; |
| dp->full_duplex = B_FALSE; |
| } else { |
| /* |
| * the link partner doesn't seem to have |
| * auto-negotiation capability and our PHY |
| * could not report current mode correctly. |
| * We guess current mode by mii_control register. |
| */ |
| val = usbgem_mii_read(dp, MII_CONTROL, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| |
| /* select 100m half or 10m half */ |
| dp->speed = (val & MII_CONTROL_100MB) ? |
| USBGEM_SPD_100 : USBGEM_SPD_10; |
| dp->full_duplex = B_FALSE; |
| 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, |
| usbgem_speed_value[dp->speed], |
| dp->full_duplex ? "full" : "half"); |
| } |
| |
| if (dp->full_duplex) { |
| dp->flow_control = |
| usbgem_fc_result[fc_cap_decode(advert)] |
| [fc_cap_decode(lpable)]; |
| } else { |
| dp->flow_control = FLOW_CONTROL_NONE; |
| } |
| dp->mii_state = MII_STATE_MEDIA_SETUP; |
| dp->mii_timer = dp->ugc.usbgc_mii_linkdown_timeout; |
| goto next_nowait; |
| |
| case MII_STATE_MEDIA_SETUP: |
| DPRINTF(2, (CE_CONT, "!%s: setup midia mode", dp->name)); |
| |
| /* assume the link state is down */ |
| dp->mii_state = MII_STATE_LINKDOWN; |
| dp->mii_supress_msg = B_FALSE; |
| |
| /* use short interval */ |
| dp->mii_interval = WATCH_INTERVAL_FAST; |
| |
| if ((!dp->anadv_autoneg) || |
| dp->ugc.usbgc_mii_an_oneshot || fix_phy) { |
| |
| /* |
| * write the result of auto negotiation back. |
| */ |
| val = usbgem_mii_read(dp, MII_CONTROL, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| 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 USBGEM_SPD_1000: |
| val |= MII_CONTROL_1000MB; |
| break; |
| |
| case USBGEM_SPD_100: |
| val |= MII_CONTROL_100MB; |
| break; |
| |
| default: |
| cmn_err(CE_WARN, "%s: unknown speed:%d", |
| dp->name, dp->speed); |
| /* FALLTHROUGH */ |
| |
| case USBGEM_SPD_10: |
| /* for USBGEM_SPD_10, do nothing */ |
| break; |
| } |
| |
| if (dp->mii_status & MII_STATUS_XSTATUS) { |
| usbgem_mii_write(dp, |
| MII_1000TC, MII_1000TC_CFG_EN, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| } |
| usbgem_mii_write(dp, MII_CONTROL, val, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| } |
| /* |
| * XXX -- nic state should be one of |
| * NIC_STATE_DISCONNECTED |
| * NIC_STATE_STOPPED |
| * NIC_STATE_INITIALIZED |
| * NIC_STATE_ONLINE |
| */ |
| if (dp->nic_state >= NIC_STATE_INITIALIZED) { |
| /* notify the result of autonegotiation to mac */ |
| if (usbgem_hal_set_media(dp) != USB_SUCCESS) { |
| goto usberr; |
| } |
| } |
| goto next_nowait; |
| |
| case MII_STATE_LINKDOWN: |
| status = usbgem_mii_read(dp, MII_STATUS, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| if (status & MII_STATUS_LINKUP) { |
| /* |
| * Link is going up |
| */ |
| dp->mii_state = MII_STATE_LINKUP; |
| dp->mii_supress_msg = B_FALSE; |
| |
| DPRINTF(0, (CE_CONT, |
| "!%s: link up detected: status:%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, |
| usbgem_speed_value[dp->speed], |
| dp->full_duplex ? "full" : "half", |
| usbgem_fc_type[dp->flow_control]); |
| |
| dp->mii_interval = |
| dp->ugc.usbgc_mii_link_watch_interval; |
| |
| if (dp->ugc.usbgc_mii_hw_link_detection && |
| dp->nic_state == NIC_STATE_ONLINE) { |
| dp->mii_interval = 0; |
| } |
| |
| if (dp->nic_state == NIC_STATE_ONLINE) { |
| if (dp->mac_state == MAC_STATE_INITIALIZED) { |
| (void) usbgem_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) { |
| /* |
| * the link down timer expired. |
| * need to restart auto-negotiation. |
| */ |
| linkdown_action = |
| dp->ugc.usbgc_mii_linkdown_timeout_action; |
| goto restart_autonego; |
| } |
| } |
| /* don't change mii_state */ |
| goto next; |
| |
| case MII_STATE_LINKUP: |
| if (rwlock == RW_READER) { |
| /* first pass, read mii status */ |
| status = usbgem_mii_read(dp, MII_STATUS, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| } |
| if ((status & MII_STATUS_LINKUP) == 0) { |
| /* |
| * Link is going down |
| */ |
| cmn_err(CE_NOTE, |
| "!%s: link down detected: status:%b", |
| dp->name, status, MII_STATUS_BITS); |
| /* |
| * Acquire exclusive lock to change mii_state |
| */ |
| if (rwlock == RW_READER) { |
| rwlock = RW_WRITER; |
| rw_exit(&dp->dev_state_lock); |
| goto again; |
| } |
| |
| dp->mii_state = MII_STATE_LINKDOWN; |
| dp->mii_timer = dp->ugc.usbgc_mii_linkdown_timeout; |
| |
| /* |
| * As we may change the state of the device, |
| * let us acquire exclusive lock for the state. |
| */ |
| if (dp->nic_state == NIC_STATE_ONLINE && |
| dp->mac_state == MAC_STATE_ONLINE && |
| dp->ugc.usbgc_mii_stop_mac_on_linkdown) { |
| (void) usbgem_restart_nic(dp); |
| /* drain tx */ |
| tx_sched = B_TRUE; |
| } |
| |
| if (dp->anadv_autoneg) { |
| /* need to restart auto-negotiation */ |
| linkdown_action = |
| dp->ugc.usbgc_mii_linkdown_action; |
| goto restart_autonego; |
| } |
| /* |
| * don't use hw link down detection until the link |
| * status become stable for a while. |
| */ |
| dp->mii_interval = |
| dp->ugc.usbgc_mii_link_watch_interval; |
| |
| goto next; |
| } |
| |
| /* |
| * still link up, no need to change mii_state |
| */ |
| if (dp->ugc.usbgc_mii_hw_link_detection && |
| dp->nic_state == NIC_STATE_ONLINE) { |
| /* |
| * no need to check link status periodicly |
| * if nic can generate interrupts when link go down. |
| */ |
| dp->mii_interval = 0; |
| } |
| goto next; |
| } |
| /* NOTREACHED */ |
| cmn_err(CE_PANIC, "!%s: %s: not reached", dp->name, __func__); |
| |
| /* |
| * Actions for new state. |
| */ |
| 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->ugc.usbgc_mii_an_oneshot) { |
| goto autonego; |
| } |
| /* PHY will restart autonego automatically */ |
| dp->mii_state = MII_STATE_AUTONEGOTIATING; |
| dp->mii_timer = dp->ugc.usbgc_mii_an_timeout; |
| dp->mii_interval = dp->ugc.usbgc_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_PANIC, "!%s: unknowm linkdown action: %d", |
| dp->name, dp->ugc.usbgc_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->ugc.usbgc_mii_reset_timeout; |
| if (!dp->ugc.usbgc_mii_dont_reset) { |
| usbgem_mii_write(dp, MII_CONTROL, MII_CONTROL_RESET, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| } |
| 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->ugc.usbgc_mii_an_timeout; |
| |
| /* start/restart autoneg */ |
| val = usbgem_mii_read(dp, MII_CONTROL, &err) & |
| ~(MII_CONTROL_ISOLATE | MII_CONTROL_PWRDN | MII_CONTROL_RESET); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| if (val & MII_CONTROL_ANE) { |
| val |= MII_CONTROL_RSAN; |
| } |
| usbgem_mii_write(dp, MII_CONTROL, |
| val | dp->ugc.usbgc_mii_an_cmd | MII_CONTROL_ANE, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| |
| dp->mii_interval = dp->ugc.usbgc_mii_an_watch_interval; |
| goto next; |
| |
| usberr: |
| dp->mii_state = MII_STATE_UNKNOWN; |
| dp->mii_interval = dp->ugc.usbgc_mii_link_watch_interval; |
| tx_sched = B_TRUE; |
| |
| next: |
| *newstatep = dp->mii_state; |
| rw_exit(&dp->dev_state_lock); |
| return (tx_sched); |
| } |
| |
| static void |
| usbgem_mii_link_watcher(struct usbgem_dev *dp) |
| { |
| int old_mii_state; |
| int new_mii_state; |
| boolean_t tx_sched; |
| |
| DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| |
| for (; ; ) { |
| |
| mutex_enter(&dp->link_watcher_lock); |
| if (dp->mii_interval) { |
| (void) cv_timedwait(&dp->link_watcher_wait_cv, |
| &dp->link_watcher_lock, |
| dp->mii_interval + ddi_get_lbolt()); |
| } else { |
| cv_wait(&dp->link_watcher_wait_cv, |
| &dp->link_watcher_lock); |
| } |
| mutex_exit(&dp->link_watcher_lock); |
| |
| if (dp->link_watcher_stop) { |
| break; |
| } |
| |
| /* we block callbacks from disconnect/suspend and restart */ |
| tx_sched = usbgem_mii_link_check(dp, |
| &old_mii_state, &new_mii_state); |
| |
| /* |
| * gld v2 notifier functions are not able to |
| * be called with any locks in this layer. |
| */ |
| if (tx_sched) { |
| /* kick potentially stopped downstream */ |
| #ifdef USBGEM_CONFIG_GLDv3 |
| mac_tx_update(dp->mh); |
| #else |
| gld_sched(dp->macinfo); |
| #endif |
| } |
| |
| if (old_mii_state != new_mii_state) { |
| /* notify new mii link state */ |
| if (new_mii_state == MII_STATE_LINKUP) { |
| dp->linkup_delay = 0; |
| USBGEM_LINKUP(dp); |
| } else if (dp->linkup_delay <= 0) { |
| USBGEM_LINKDOWN(dp); |
| } |
| } else if (dp->linkup_delay < 0) { |
| /* first linkup timeout */ |
| dp->linkup_delay = 0; |
| USBGEM_LINKDOWN(dp); |
| } |
| } |
| |
| thread_exit(); |
| } |
| |
| void |
| usbgem_mii_update_link(struct usbgem_dev *dp) |
| { |
| cv_signal(&dp->link_watcher_wait_cv); |
| } |
| |
| int |
| usbgem_mii_probe_default(struct usbgem_dev *dp) |
| { |
| int phy; |
| uint16_t status; |
| uint16_t xstatus; |
| int err; |
| uint16_t adv; |
| uint16_t adv_org; |
| |
| DPRINTF(3, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| |
| /* |
| * Scan PHY |
| */ |
| dp->mii_status = 0; |
| |
| /* Try default phy first */ |
| if (dp->mii_phy_addr) { |
| status = usbgem_mii_read(dp, MII_STATUS, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| if (status != 0xffff && status != 0x0000) { |
| 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 (USB_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->ugc.usbgc_mii_addr_min; phy < 32; phy++) { |
| dp->mii_phy_addr = phy; |
| status = usbgem_mii_read(dp, MII_STATUS, &err); |
| if (err != USB_SUCCESS) { |
| DPRINTF(0, (CE_CONT, |
| "!%s: %s: mii_read(status) failed", |
| dp->name, __func__)); |
| goto usberr; |
| } |
| |
| if (status != 0xffff && status != 0x0000) { |
| usbgem_mii_write(dp, MII_CONTROL, 0, &err); |
| if (err != USB_SUCCESS) { |
| DPRINTF(0, (CE_CONT, |
| "!%s: %s: mii_write(control) failed", |
| dp->name, __func__)); |
| goto usberr; |
| } |
| goto PHY_found; |
| } |
| } |
| for (phy = dp->ugc.usbgc_mii_addr_min; phy < 32; phy++) { |
| dp->mii_phy_addr = phy; |
| usbgem_mii_write(dp, MII_CONTROL, 0, &err); |
| if (err != USB_SUCCESS) { |
| DPRINTF(0, (CE_CONT, |
| "!%s: %s: mii_write(control) failed", |
| dp->name, __func__)); |
| goto usberr; |
| } |
| status = usbgem_mii_read(dp, MII_STATUS, &err); |
| if (err != USB_SUCCESS) { |
| DPRINTF(0, (CE_CONT, |
| "!%s: %s: mii_read(status) failed", |
| dp->name, __func__)); |
| goto usberr; |
| } |
| |
| if (status != 0xffff && status != 0) { |
| goto PHY_found; |
| } |
| } |
| |
| cmn_err(CE_NOTE, "!%s: no MII PHY found", dp->name); |
| return (USB_FAILURE); |
| |
| PHY_found: |
| dp->mii_status = status; |
| dp->mii_status_ro = ~status; |
| dp->mii_phy_id = usbgem_mii_read(dp, MII_PHYIDH, &err) << 16; |
| if (err != USB_SUCCESS) { |
| DPRINTF(0, (CE_CONT, |
| "!%s: %s: mii_read(PHYIDH) failed", |
| dp->name, __func__)); |
| goto usberr; |
| } |
| dp->mii_phy_id |= usbgem_mii_read(dp, MII_PHYIDL, &err); |
| if (err != USB_SUCCESS) { |
| DPRINTF(0, (CE_CONT, |
| "!%s: %s: mii_read(PHYIDL) failed", |
| dp->name, __func__)); |
| goto usberr; |
| } |
| |
| 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, exp:%b", |
| dp->name, |
| usbgem_mii_read(dp, MII_CONTROL, &err), MII_CONTROL_BITS, |
| status, MII_STATUS_BITS, |
| usbgem_mii_read(dp, MII_AN_ADVERT, &err), MII_ABILITY_BITS, |
| usbgem_mii_read(dp, MII_AN_LPABLE, &err), MII_ABILITY_BITS, |
| usbgem_mii_read(dp, MII_AN_EXPANSION, &err), MII_AN_EXP_BITS); |
| |
| dp->mii_xstatus = 0; |
| if (status & MII_STATUS_XSTATUS) { |
| dp->mii_xstatus = usbgem_mii_read(dp, MII_XSTATUS, &err); |
| |
| cmn_err(CE_CONT, "!%s: xstatus:%b", |
| dp->name, dp->mii_xstatus, MII_XSTATUS_BITS); |
| } |
| dp->mii_xstatus_ro = ~dp->mii_xstatus; |
| |
| /* check if the phy can advertize pause abilities */ |
| adv_org = usbgem_mii_read(dp, MII_AN_ADVERT, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| |
| usbgem_mii_write(dp, MII_AN_ADVERT, |
| MII_ABILITY_PAUSE | MII_ABILITY_ASM_DIR, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| |
| adv = usbgem_mii_read(dp, MII_AN_ADVERT, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| |
| if ((adv & MII_ABILITY_PAUSE) == 0) { |
| dp->ugc.usbgc_flow_control &= ~1; |
| } |
| |
| if ((adv & MII_ABILITY_ASM_DIR) == 0) { |
| dp->ugc.usbgc_flow_control &= ~2; |
| } |
| |
| usbgem_mii_write(dp, MII_AN_ADVERT, adv_org, &err); |
| if (err != USB_SUCCESS) { |
| goto usberr; |
| } |
| return (USB_SUCCESS); |
| |
| usberr: |
| return (USB_FAILURE); |
| } |
| |
| int |
| usbgem_mii_init_default(struct usbgem_dev *dp) |
| { |
| /* ENPTY */ |
| return (USB_SUCCESS); |
| } |
| |
| static int |
| usbgem_mii_start(struct usbgem_dev *dp) |
| { |
| int err; |
| kthread_t *lwth; |
| |
| DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| |
| /* make a first call of usbgem_mii_link_check() */ |
| dp->link_watcher_stop = 0; |
| dp->mii_state = MII_STATE_UNKNOWN; |
| dp->mii_interval = drv_usectohz(1000*1000); /* 1sec */ |
| dp->mii_last_check = ddi_get_lbolt(); |
| dp->linkup_delay = 600 * drv_usectohz(1000*1000); /* 10 minutes */ |
| |
| lwth = thread_create(NULL, 0, usbgem_mii_link_watcher, dp, 0, &p0, |
| TS_RUN, minclsyspri); |
| if (lwth == NULL) { |
| cmn_err(CE_WARN, |
| "!%s: %s: failed to create a link watcher thread", |
| dp->name, __func__); |
| return (USB_FAILURE); |
| } |
| dp->link_watcher_did = lwth->t_did; |
| |
| return (USB_SUCCESS); |
| } |
| |
| static void |
| usbgem_mii_stop(struct usbgem_dev *dp) |
| { |
| DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| |
| /* Ensure timer routine stopped */ |
| dp->link_watcher_stop = 1; |
| cv_signal(&dp->link_watcher_wait_cv); |
| thread_join(dp->link_watcher_did); |
| } |
| |
| /* ============================================================== */ |
| /* |
| * internal mac register operation interface |
| */ |
| /* ============================================================== */ |
| /* |
| * usbgem_mac_init: cold start |
| */ |
| static int |
| usbgem_mac_init(struct usbgem_dev *dp) |
| { |
| int err; |
| |
| DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| |
| if (dp->mac_state == MAC_STATE_DISCONNECTED) { |
| /* pretend we succeeded */ |
| return (USB_SUCCESS); |
| } |
| |
| ASSERT(dp->mac_state == MAC_STATE_STOPPED); |
| |
| /* reset fatal error timestamp */ |
| dp->fatal_error = (clock_t)0; |
| |
| /* reset tx side state */ |
| mutex_enter(&dp->txlock); |
| dp->tx_busy_cnt = 0; |
| dp->tx_max_packets = dp->ugc.usbgc_tx_list_max; |
| mutex_exit(&dp->txlock); |
| |
| /* reset rx side state */ |
| mutex_enter(&dp->rxlock); |
| dp->rx_busy_cnt = 0; |
| mutex_exit(&dp->rxlock); |
| |
| err = usbgem_hal_init_chip(dp); |
| if (err == USB_SUCCESS) { |
| dp->mac_state = MAC_STATE_INITIALIZED; |
| } |
| |
| return (err); |
| } |
| |
| /* |
| * usbgem_mac_start: warm start |
| */ |
| static int |
| usbgem_mac_start(struct usbgem_dev *dp) |
| { |
| int err; |
| int i; |
| usb_flags_t flags = 0; |
| usb_intr_req_t *req; |
| #ifdef USBGEM_DEBUG_LEVEL |
| usb_pipe_state_t p_state; |
| #endif |
| DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| |
| if (dp->mac_state == MAC_STATE_DISCONNECTED) { |
| /* do nothing but don't return failure */ |
| return (USB_SUCCESS); |
| } |
| |
| if (dp->mac_state != MAC_STATE_INITIALIZED) { |
| /* don't return failer */ |
| DPRINTF(0, (CE_CONT, |
| "!%s: %s: mac_state(%d) is not MAC_STATE_INITIALIZED", |
| dp->name, __func__, dp->mac_state)); |
| goto x; |
| } |
| |
| dp->mac_state = MAC_STATE_ONLINE; |
| |
| if (usbgem_hal_start_chip(dp) != USB_SUCCESS) { |
| cmn_err(CE_NOTE, |
| "!%s: %s: usb error was detected during start_chip", |
| dp->name, __func__); |
| goto x; |
| } |
| |
| #ifdef USBGEM_DEBUG_LEVEL |
| usb_pipe_get_state(dp->intr_pipe, &p_state, 0); |
| ASSERT(p_state == USB_PIPE_STATE_IDLE); |
| #endif /* USBGEM_DEBUG_LEVEL */ |
| |
| if (dp->ugc.usbgc_interrupt && dp->intr_pipe) { |
| |
| /* make a request for interrupt */ |
| |
| req = usb_alloc_intr_req(dp->dip, 0, USB_FLAGS_SLEEP); |
| if (req == NULL) { |
| cmn_err(CE_WARN, "!%s: %s: failed to allocate intreq", |
| dp->name, __func__); |
| goto x; |
| } |
| req->intr_data = NULL; |
| req->intr_client_private = (usb_opaque_t)dp; |
| req->intr_timeout = 0; |
| req->intr_attributes = |
| USB_ATTRS_SHORT_XFER_OK | USB_ATTRS_AUTOCLEARING; |
| req->intr_len = dp->ep_intr->wMaxPacketSize; |
| req->intr_cb = usbgem_intr_cb; |
| req->intr_exc_cb = usbgem_intr_cb; |
| req->intr_completion_reason = 0; |
| req->intr_cb_flags = 0; |
| |
| err = usb_pipe_intr_xfer(dp->intr_pipe, req, flags); |
| if (err != USB_SUCCESS) { |
| cmn_err(CE_WARN, |
| "%s: err:%d failed to start polling of intr pipe", |
| dp->name, err); |
| goto x; |
| } |
| } |
| |
| /* kick to receive the first packet */ |
| if (usbgem_init_rx_buf(dp) != USB_SUCCESS) { |
| goto err_stop_intr; |
| } |
| dp->rx_active = B_TRUE; |
| |
| return (USB_SUCCESS); |
| |
| err_stop_intr: |
| /* stop the interrupt pipe */ |
| DPRINTF(0, (CE_CONT, "!%s: %s: FAULURE", dp->name, __func__)); |
| if (dp->ugc.usbgc_interrupt && dp->intr_pipe) { |
| usb_pipe_stop_intr_polling(dp->intr_pipe, USB_FLAGS_SLEEP); |
| } |
| x: |
| ASSERT(dp->mac_state == MAC_STATE_ONLINE); |
| /* we use another flag to indicate error state. */ |
| if (dp->fatal_error == (clock_t)0) { |
| dp->fatal_error = usbgem_timestamp_nz(); |
| } |
| return (USB_FAILURE); |
| } |
| |
| static int |
| usbgem_mac_stop(struct usbgem_dev *dp, int new_state, boolean_t graceful) |
| { |
| DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| |
| /* |
| * we must have writer lock for dev_state_lock |
| */ |
| ASSERT(new_state == MAC_STATE_STOPPED || |
| new_state == MAC_STATE_DISCONNECTED); |
| |
| /* stop polling interrupt pipe */ |
| if (dp->ugc.usbgc_interrupt && dp->intr_pipe) { |
| usb_pipe_stop_intr_polling(dp->intr_pipe, USB_FLAGS_SLEEP); |
| } |
| |
| if (new_state == MAC_STATE_STOPPED || graceful) { |
| /* stop the nic hardware completely */ |
| if (usbgem_hal_stop_chip(dp) != USB_SUCCESS) { |
| (void) usbgem_hal_reset_chip(dp); |
| } |
| } |
| |
| /* stop preparing new rx packets and sending new packets */ |
| dp->mac_state = new_state; |
| |
| /* other processors must get mac_state correctly after here */ |
| membar_producer(); |
| |
| /* cancel all requests we have sent */ |
| usb_pipe_reset(dp->dip, dp->bulkin_pipe, USB_FLAGS_SLEEP, NULL, 0); |
| usb_pipe_reset(dp->dip, dp->bulkout_pipe, USB_FLAGS_SLEEP, NULL, 0); |
| |
| DPRINTF(0, (CE_CONT, |
| "!%s: %s: rx_busy_cnt:%d tx_busy_cnt:%d", |
| dp->name, __func__, dp->rx_busy_cnt, dp->tx_busy_cnt)); |
| |
| /* |
| * Here all rx packets has been cancelled and their call back |
| * function has been exeuted, because we called usb_pipe_reset |
| * synchronously. |
| * So actually we just ensure rx_busy_cnt == 0. |
| */ |
| mutex_enter(&dp->rxlock); |
| while (dp->rx_busy_cnt > 0) { |
| cv_wait(&dp->rx_drain_cv, &dp->rxlock); |
| } |
| mutex_exit(&dp->rxlock); |
| |
| DPRINTF(0, (CE_CONT, "!%s: %s: rx_busy_cnt is %d now", |
| dp->name, __func__, dp->rx_busy_cnt)); |
| |
| mutex_enter(&dp->txlock); |
| while (dp->tx_busy_cnt > 0) { |
| cv_wait(&dp->tx_drain_cv, &dp->txlock); |
| } |
| mutex_exit(&dp->txlock); |
| |
| DPRINTF(0, (CE_CONT, "!%s: %s: tx_busy_cnt is %d now", |
| dp->name, __func__, dp->tx_busy_cnt)); |
| |
| return (USB_SUCCESS); |
| } |
| |
| static int |
| usbgem_add_multicast(struct usbgem_dev *dp, const uint8_t *ep) |
| { |
| int cnt; |
| int err; |
| |
| DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); |
| |
| sema_p(&dp->rxfilter_lock); |
| if (dp->mc_count_req++ < USBGEM_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->ugc.usbgc_multicast_hash) { |
| dp->mc_list[cnt].hash = |
| (*dp->ugc.usbgc_multicast_hash)(dp, 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; |
| } |
| |
| if (dp->mac_state != MAC_STATE_DISCONNECTED) { |
| /* tell new multicast list to the hardware */ |
| err = usbgem_hal_set_rx_filter(dp); |
| } |
| sema_v(&dp->rxfilter_lock); |
| |
| return (err); |
| } |
| |
| static int |
| usbgem_remove_multicast(struct usbgem_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__)); |
| |
| sema_p(&dp->rxfilter_lock); |
| 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; |
| } |
| |
| if (dp->mac_state != MAC_STATE_DISCONNECTED) { |
| err = usbgem_hal_set_rx_filter(dp); |
| } |
| sema_v(&dp->rxfilter_lock); |
| |
| return (err); |
| } |
| |
| |
| /* ============================================================== */ |
| /* |
| * ioctl |
| */ |
| /* ============================================================== */ |
| 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 */ |
| }; |
| |
| |
| #ifdef USBGEM_CONFIG_MAC_PROP |
| static int |
| usbgem_get_def_val(struct usbgem_dev *dp, mac_prop_id_t pr_num, |
| uint_t pr_valsize, void *pr_val) |
| { |
| link_flowctrl_t fl; |
| int err = 0; |
| |
| ASSERT(pr_valsize > 0); |
| switch (pr_num) { |
| case MAC_PROP_AUTONEG: |
| *(uint8_t *)pr_val = |
| BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG); |
| break; |
| |
| case MAC_PROP_FLOWCTRL: |
| if (pr_valsize < sizeof (link_flowctrl_t)) { |
| return (EINVAL); |
| } |
| switch (dp->ugc.usbgc_flow_control) { |
| case FLOW_CONTROL_NONE: |
| fl = LINK_FLOWCTRL_NONE; |
| break; |
| case FLOW_CONTROL_SYMMETRIC: |
| fl = LINK_FLOWCTRL_BI; |
| break; |
| case FLOW_CONTROL_TX_PAUSE: |
| fl = LINK_FLOWCTRL_TX; |
| break; |
| case FLOW_CONTROL_RX_PAUSE: |
| fl = LINK_FLOWCTRL_RX; |
| break; |
| } |
| bcopy(&fl, pr_val, sizeof (fl)); |
| break; |
| |
| case MAC_PROP_ADV_1000FDX_CAP: |
| case MAC_PROP_EN_1000FDX_CAP: |
| *(uint8_t *)pr_val = |
| (dp->mii_xstatus & MII_XSTATUS_1000BASET_FD) || |
| (dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD); |
| break; |
| |
| case MAC_PROP_ADV_1000HDX_CAP: |
| case MAC_PROP_EN_1000HDX_CAP: |
| *(uint8_t *)pr_val = |
| (dp->mii_xstatus & MII_XSTATUS_1000BASET) || |
| (dp->mii_xstatus & MII_XSTATUS_1000BASEX); |
| break; |
| |
| case MAC_PROP_ADV_100T4_CAP: |
| case MAC_PROP_EN_100T4_CAP: |
| *(uint8_t *)pr_val = |
| BOOLEAN(dp->mii_status & MII_STATUS_100_BASE_T4); |
| break; |
| |
| case MAC_PROP_ADV_100FDX_CAP: |
| case MAC_PROP_EN_100FDX_CAP: |
| *(uint8_t *)pr_val = |
| BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD); |
| break; |
| |
| case MAC_PROP_ADV_100HDX_CAP: |
| case MAC_PROP_EN_100HDX_CAP: |
| *(uint8_t *)pr_val = |
| BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX); |
| break; |
| |
| case MAC_PROP_ADV_10FDX_CAP: |
| case MAC_PROP_EN_10FDX_CAP: |
| *(uint8_t *)pr_val = |
| BOOLEAN(dp->mii_status & MII_STATUS_10_FD); |
| break; |
| |
| case MAC_PROP_ADV_10HDX_CAP: |
| case MAC_PROP_EN_10HDX_CAP: |
| *(uint8_t *)pr_val = |
| BOOLEAN(dp->mii_status & MII_STATUS_10); |
| break; |
| |
| default: |
| err = ENOTSUP; |
| break; |
| } |
| return (err); |
| } |
| |
| #ifdef MAC_VERSION_V1 |
| static void |
| usbgem_m_propinfo(void *arg, const char *pr_name, mac_prop_id_t pr_num, |
| mac_prop_info_handle_t prh) |
| { |
| struct usbgem_dev *dp = arg; |
| link_flowctrl_t fl; |
| |
| /* |
| * By default permissions are read/write unless specified |
| * otherwise by the driver. |
| */ |
| |
| switch (pr_num) { |
| case MAC_PROP_DUPLEX: |
| case MAC_PROP_SPEED: |
| case MAC_PROP_STATUS: |
| case MAC_PROP_ADV_1000FDX_CAP: |
| case MAC_PROP_ADV_1000HDX_CAP: |
| case MAC_PROP_ADV_100FDX_CAP: |
| case MAC_PROP_ADV_100HDX_CAP: |
| case MAC_PROP_ADV_10FDX_CAP: |
| case MAC_PROP_ADV_10HDX_CAP: |
| case MAC_PROP_ADV_100T4_CAP: |
| case MAC_PROP_EN_100T4_CAP: |
| mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); |
| break; |
| |
| case MAC_PROP_EN_1000FDX_CAP: |
| if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET_FD) == 0) { |
| mac_prop_info_set_default_uint8(prh, |
| BOOLEAN( |
| dp->mii_xstatus & MII_XSTATUS_1000BASET_FD)); |
| } else if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX_FD) |
| == 0) { |
| mac_prop_info_set_default_uint8(prh, |
| BOOLEAN( |
| dp->mii_xstatus & MII_XSTATUS_1000BASEX_FD)); |
| } else { |
| mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); |
| } |
| break; |
| |
| case MAC_PROP_EN_1000HDX_CAP: |
| if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET) == 0) { |
| mac_prop_info_set_default_uint8(prh, |
| BOOLEAN( |
| dp->mii_xstatus & MII_XSTATUS_1000BASET)); |
| } else if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX) == 0) { |
| |