/* * 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. */ /* * Copyright 2019 Joyent, Inc. * Copyright 2022 Garrett D'Amore */ /* * Change log */ /* * TODO: * implement DELAYED_START */ /* * 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 /* supplement definitions */ extern const char *usb_str_cr(usb_cr_t); #include #include "usbgem_mii.h" #include "usbgem.h" char ident[] = "usb general ethernet mac driver v" VERSION; /* 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 }; 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)); } /* ============================================================== */ /* * 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); } /* ============================================================== */ /* * 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: 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 = 0; } } /* ================================================================== */ /* * 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 */ mac_rx(dp->mh, NULL, newmp); } 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) { mac_tx_update(dp->mh); } } 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", }; #define USBGEM_LINKUP(dp) mac_link_update((dp)->mh, LINK_STATE_UP) #define USBGEM_LINKDOWN(dp) mac_link_update((dp)->mh, LINK_STATE_DOWN) 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); } /* 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 */ mac_tx_update(dp->mh); } 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 */ }; 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); } 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) { mac_prop_info_set_default_uint8(prh, BOOLEAN( dp->mii_xstatus & MII_XSTATUS_1000BASEX)); } else { mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); } break; case MAC_PROP_EN_100FDX_CAP: if ((dp->mii_status_ro & MII_STATUS_100_BASEX_FD) == 0) { mac_prop_info_set_default_uint8(prh, BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX_FD)); } else { mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); } break; case MAC_PROP_EN_100HDX_CAP: if ((dp->mii_status_ro & MII_STATUS_100_BASEX) == 0) { mac_prop_info_set_default_uint8(prh, BOOLEAN(dp->mii_status & MII_STATUS_100_BASEX)); } else { mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); } break; case MAC_PROP_EN_10FDX_CAP: if ((dp->mii_status_ro & MII_STATUS_10_FD) == 0) { mac_prop_info_set_default_uint8(prh, BOOLEAN(dp->mii_status & MII_STATUS_10_FD)); } else { mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); } break; case MAC_PROP_EN_10HDX_CAP: if ((dp->mii_status_ro & MII_STATUS_10) == 0) { mac_prop_info_set_default_uint8(prh, BOOLEAN(dp->mii_status & MII_STATUS_10)); } else { mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); } break; case MAC_PROP_AUTONEG: if ((dp->mii_status_ro & MII_STATUS_CANAUTONEG) == 0) { mac_prop_info_set_default_uint8(prh, BOOLEAN(dp->mii_status & MII_STATUS_CANAUTONEG)); } else { mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); } break; case MAC_PROP_FLOWCTRL: 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; } mac_prop_info_set_default_link_flowctrl(prh, fl); break; case MAC_PROP_MTU: mac_prop_info_set_range_uint32(prh, dp->ugc.usbgc_min_mtu, dp->ugc.usbgc_max_mtu); break; case MAC_PROP_PRIVATE: break; } } static int usbgem_m_setprop(void *arg, const char *pr_name, mac_prop_id_t pr_num, uint_t pr_valsize, const void *pr_val) { struct usbgem_dev *dp = arg; int err = 0; boolean_t update = B_FALSE; link_flowctrl_t flowctrl; uint32_t cur_mtu, new_mtu; rw_enter(&dp->dev_state_lock, RW_WRITER); switch (pr_num) { case MAC_PROP_EN_1000FDX_CAP: if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET_FD) == 0 || (dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX_FD) == 0) { if (dp->anadv_1000fdx != *(uint8_t *)pr_val) { dp->anadv_1000fdx = *(uint8_t *)pr_val; update = B_TRUE; } } else { err = ENOTSUP; } break; case MAC_PROP_EN_1000HDX_CAP: if ((dp->mii_xstatus_ro & MII_XSTATUS_1000BASET) == 0 || (dp->mii_xstatus_ro & MII_XSTATUS_1000BASEX) == 0) { if (dp->anadv_1000hdx != *(uint8_t *)pr_val) { dp->anadv_1000hdx = *(uint8_t *)pr_val; update = B_TRUE; } } else { err = ENOTSUP; } break; case MAC_PROP_EN_100FDX_CAP: if ((dp->mii_status_ro & MII_STATUS_100_BASEX_FD) == 0) { if (dp->anadv_100fdx != *(uint8_t *)pr_val) { dp->anadv_100fdx = *(uint8_t *)pr_val; update = B_TRUE; } } else { err = ENOTSUP; } break; case MAC_PROP_EN_100HDX_CAP: if ((dp->mii_status_ro & MII_STATUS_100_BASEX) == 0) { if (dp->anadv_100hdx != *(uint8_t *)pr_val) { dp->anadv_100hdx = *(uint8_t *)pr_val; update = B_TRUE; } } else { err = ENOTSUP; } break; case MAC_PROP_EN_10FDX_CAP: if ((dp->mii_status_ro & MII_STATUS_10_FD) == 0) { if (dp->anadv_10fdx != *(uint8_t *)pr_val) { dp->anadv_10fdx = *(uint8_t *)pr_val; update = B_TRUE; } } else { err = ENOTSUP; } break; case MAC_PROP_EN_10HDX_CAP: if ((dp->mii_status_ro & MII_STATUS_10_FD) == 0) { if (dp->anadv_10hdx != *(uint8_t *)pr_val) { dp->anadv_10hdx = *(uint8_t *)pr_val; update = B_TRUE; } } else { err = ENOTSUP; } break; case MAC_PROP_AUTONEG: if ((dp->mii_status_ro & MII_STATUS_CANAUTONEG) == 0) { if (dp->anadv_autoneg != *(uint8_t *)pr_val) { dp->anadv_autoneg = *(uint8_t *)pr_val; update = B_TRUE; } } else { err = ENOTSUP; } break; case MAC_PROP_FLOWCTRL: bcopy(pr_val, &flowctrl, sizeof (flowctrl)); switch (flowctrl) { default: err = EINVAL; break; case LINK_FLOWCTRL_NONE: if (dp->flow_control != FLOW_CONTROL_NONE) { dp->flow_control = FLOW_CONTROL_NONE; update = B_TRUE; } break; case LINK_FLOWCTRL_RX: if (dp->flow_control != FLOW_CONTROL_RX_PAUSE) { dp->flow_control = FLOW_CONTROL_RX_PAUSE; update = B_TRUE; } break; case LINK_FLOWCTRL_TX: if (dp->flow_control != FLOW_CONTROL_TX_PAUSE) { dp->flow_control = FLOW_CONTROL_TX_PAUSE; update = B_TRUE; } break; case LINK_FLOWCTRL_BI: if (dp->flow_control != FLOW_CONTROL_SYMMETRIC) { dp->flow_control = FLOW_CONTROL_SYMMETRIC; update = B_TRUE; } break; } break; 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_STATUS: case MAC_PROP_SPEED: case MAC_PROP_DUPLEX: err = ENOTSUP; /* read-only prop. Can't set this. */ break; case MAC_PROP_MTU: bcopy(pr_val, &new_mtu, sizeof (new_mtu)); if (new_mtu != dp->mtu) { err = EINVAL; } break; case MAC_PROP_PRIVATE: err = ENOTSUP; break; default: err = ENOTSUP; break; } if (update) { /* sync with PHY */ usbgem_choose_forcedmode(dp); dp->mii_state = MII_STATE_UNKNOWN; cv_signal(&dp->link_watcher_wait_cv); } rw_exit(&dp->dev_state_lock); return (err); } static int usbgem_m_getprop(void *arg, const char *pr_name, mac_prop_id_t pr_num, uint_t pr_valsize, void *pr_val) { struct usbgem_dev *dp = arg; int err = 0; link_flowctrl_t flowctrl; uint64_t tmp = 0; if (pr_valsize == 0) { return (EINVAL); } bzero(pr_val, pr_valsize); rw_enter(&dp->dev_state_lock, RW_READER); switch (pr_num) { case MAC_PROP_DUPLEX: if (pr_valsize >= sizeof (link_duplex_t)) { if (dp->mii_state != MII_STATE_LINKUP) { *(link_duplex_t *)pr_val = LINK_DUPLEX_UNKNOWN; } else if (dp->full_duplex) { *(link_duplex_t *)pr_val = LINK_DUPLEX_FULL; } else { *(link_duplex_t *)pr_val = LINK_DUPLEX_HALF; } } else { err = EINVAL; } break; case MAC_PROP_SPEED: if (pr_valsize >= sizeof (uint64_t)) { switch (dp->speed) { case USBGEM_SPD_1000: tmp = 1000000000; break; case USBGEM_SPD_100: tmp = 100000000; break; case USBGEM_SPD_10: tmp = 10000000; break; default: tmp = 0; } bcopy(&tmp, pr_val, sizeof (tmp)); } else { err = EINVAL; } break; case MAC_PROP_AUTONEG: *(uint8_t *)pr_val = dp->anadv_autoneg; break; case MAC_PROP_FLOWCTRL: if (pr_valsize >= sizeof (link_flowctrl_t)) { switch (dp->flow_control) { case FLOW_CONTROL_NONE: flowctrl = LINK_FLOWCTRL_NONE; break; case FLOW_CONTROL_RX_PAUSE: flowctrl = LINK_FLOWCTRL_RX; break; case FLOW_CONTROL_TX_PAUSE: flowctrl = LINK_FLOWCTRL_TX; break; case FLOW_CONTROL_SYMMETRIC: flowctrl = LINK_FLOWCTRL_BI; break; } bcopy(&flowctrl, pr_val, sizeof (flowctrl)); } else { err = EINVAL; } break; 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: usbgem_get_def_val(dp, pr_num, pr_valsize, pr_val); break; case MAC_PROP_EN_1000FDX_CAP: *(uint8_t *)pr_val = dp->anadv_1000fdx; break; case MAC_PROP_EN_1000HDX_CAP: *(uint8_t *)pr_val = dp->anadv_1000hdx; break; case MAC_PROP_EN_100FDX_CAP: *(uint8_t *)pr_val = dp->anadv_100fdx; break; case MAC_PROP_EN_100HDX_CAP: *(uint8_t *)pr_val = dp->anadv_100hdx; break; case MAC_PROP_EN_10FDX_CAP: *(uint8_t *)pr_val = dp->anadv_10fdx; break; case MAC_PROP_EN_10HDX_CAP: *(uint8_t *)pr_val = dp->anadv_10hdx; break; case MAC_PROP_EN_100T4_CAP: *(uint8_t *)pr_val = dp->anadv_100t4; break; case MAC_PROP_PRIVATE: err = ENOTSUP; break; default: err = ENOTSUP; break; } rw_exit(&dp->dev_state_lock); return (err); } static void usbgem_mac_ioctl(struct usbgem_dev *dp, queue_t *wq, mblk_t *mp) { struct iocblk *iocp; enum ioc_reply status; DPRINTF(1, (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; DPRINTF(1, (CE_CONT, "%s: %s cmd:0x%x", dp->name, __func__, iocp->ioc_cmd)); miocnak(wq, mp, 0, EINVAL); } static int usbgem_mac_xcvr_inuse(struct usbgem_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 usbgem_m_getstat(void *, uint_t, uint64_t *); static int usbgem_m_start(void *); static void usbgem_m_stop(void *); static int usbgem_m_setpromisc(void *, boolean_t); static int usbgem_m_multicst(void *, boolean_t, const uint8_t *); static int usbgem_m_unicst(void *, const uint8_t *); static mblk_t *usbgem_m_tx(void *, mblk_t *); static void usbgem_m_ioctl(void *, queue_t *, mblk_t *); static int usbgem_m_setprop(void *, const char *, mac_prop_id_t, uint_t, const void *); static int usbgem_m_getprop(void *, const char *, mac_prop_id_t, uint_t, void *); static mac_callbacks_t gem_m_callbacks = { MC_IOCTL | MC_SETPROP | MC_GETPROP | MC_PROPINFO, usbgem_m_getstat, usbgem_m_start, usbgem_m_stop, usbgem_m_setpromisc, usbgem_m_multicst, usbgem_m_unicst, usbgem_m_tx, NULL, usbgem_m_ioctl, NULL, /* m_getcapab */ NULL, NULL, usbgem_m_setprop, usbgem_m_getprop, usbgem_m_propinfo, }; static int usbgem_m_start(void *arg) { int ret; int err; struct usbgem_dev *dp = arg; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); err = EIO; rw_enter(&dp->dev_state_lock, RW_WRITER); dp->nic_state = NIC_STATE_ONLINE; if (dp->mac_state == MAC_STATE_DISCONNECTED) { err = 0; goto x; } if (usbgem_mac_init(dp) != USB_SUCCESS) { goto x; } /* initialize rx filter state */ sema_p(&dp->rxfilter_lock); dp->mc_count = 0; dp->mc_count_req = 0; bcopy(dp->dev_addr.ether_addr_octet, dp->cur_addr.ether_addr_octet, ETHERADDRL); dp->rxmode |= RXMODE_ENABLE; ret = usbgem_hal_set_rx_filter(dp); sema_v(&dp->rxfilter_lock); if (ret != USB_SUCCESS) { goto x; } if (dp->mii_state == MII_STATE_LINKUP) { /* setup media mode if the link have been up */ if (usbgem_hal_set_media(dp) != USB_SUCCESS) { goto x; } if (usbgem_mac_start(dp) != USB_SUCCESS) { goto x; } } err = 0; x: rw_exit(&dp->dev_state_lock); return (err); } static void usbgem_m_stop(void *arg) { struct usbgem_dev *dp = arg; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); /* stop rx gracefully */ rw_enter(&dp->dev_state_lock, RW_READER); sema_p(&dp->rxfilter_lock); dp->rxmode &= ~RXMODE_ENABLE; if (dp->mac_state != MAC_STATE_DISCONNECTED) { (void) usbgem_hal_set_rx_filter(dp); } sema_v(&dp->rxfilter_lock); rw_exit(&dp->dev_state_lock); /* make the nic state inactive */ rw_enter(&dp->dev_state_lock, RW_WRITER); dp->nic_state = NIC_STATE_STOPPED; /* stop mac completely */ if (dp->mac_state != MAC_STATE_DISCONNECTED) { (void) usbgem_mac_stop(dp, MAC_STATE_STOPPED, STOP_GRACEFUL); } rw_exit(&dp->dev_state_lock); } static int usbgem_m_multicst(void *arg, boolean_t add, const uint8_t *ep) { int err; int ret; struct usbgem_dev *dp = arg; DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); rw_enter(&dp->dev_state_lock, RW_READER); if (add) { ret = usbgem_add_multicast(dp, ep); } else { ret = usbgem_remove_multicast(dp, ep); } rw_exit(&dp->dev_state_lock); err = 0; if (ret != USB_SUCCESS) { err = EIO; } return (err); } static int usbgem_m_setpromisc(void *arg, boolean_t on) { int err; struct usbgem_dev *dp = arg; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); rw_enter(&dp->dev_state_lock, RW_READER); sema_p(&dp->rxfilter_lock); if (on) { dp->rxmode |= RXMODE_PROMISC; } else { dp->rxmode &= ~RXMODE_PROMISC; } err = 0; if (dp->mac_state != MAC_STATE_DISCONNECTED) { if (usbgem_hal_set_rx_filter(dp) != USB_SUCCESS) { err = EIO; } } sema_v(&dp->rxfilter_lock); rw_exit(&dp->dev_state_lock); return (err); } int usbgem_m_getstat(void *arg, uint_t stat, uint64_t *valp) { uint64_t val; struct usbgem_dev *dp = arg; struct usbgem_stats *gstp = &dp->stats; DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); rw_enter(&dp->dev_state_lock, RW_READER); if (dp->mac_state == MAC_STATE_DISCONNECTED) { rw_exit(&dp->dev_state_lock); return (0); } (void) usbgem_hal_get_stats(dp); rw_exit(&dp->dev_state_lock); switch (stat) { case MAC_STAT_IFSPEED: val = usbgem_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 = usbgem_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 = dp->ugc.usbgc_flow_control > FLOW_CONTROL_SYMMETRIC; break; case ETHER_STAT_CAP_PAUSE: val = dp->ugc.usbgc_flow_control != FLOW_CONTROL_NONE; 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 = dp->anadv_asmpause; break; case ETHER_STAT_ADV_CAP_PAUSE: val = dp->anadv_pause; 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; #ifdef NEVER /* it doesn't make sense */ case ETHER_STAT_CAP_REMFAULT: val = B_TRUE; break; case ETHER_STAT_ADV_REMFAULT: val = dp->anadv_remfault; break; #endif 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 *valp = 0; return (ENOTSUP); } *valp = val; return (0); } static int usbgem_m_unicst(void *arg, const uint8_t *mac) { int err; struct usbgem_dev *dp = arg; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); rw_enter(&dp->dev_state_lock, RW_READER); sema_p(&dp->rxfilter_lock); bcopy(mac, dp->cur_addr.ether_addr_octet, ETHERADDRL); dp->rxmode |= RXMODE_ENABLE; err = 0; if (dp->mac_state != MAC_STATE_DISCONNECTED) { if (usbgem_hal_set_rx_filter(dp) != USB_SUCCESS) { err = EIO; } } sema_v(&dp->rxfilter_lock); rw_exit(&dp->dev_state_lock); return (err); } /* * usbgem_m_tx is used only for sending data packets into ethernet wire. */ static mblk_t * usbgem_m_tx(void *arg, mblk_t *mp_head) { int limit; mblk_t *mp; mblk_t *nmp; struct usbgem_dev *dp = arg; DPRINTF(4, (CE_CONT, "!%s: %s: called", dp->name, __func__)); mp = mp_head; rw_enter(&dp->dev_state_lock, RW_READER); if (dp->mii_state != MII_STATE_LINKUP || dp->mac_state != MAC_STATE_ONLINE) { /* some nics hate to send packets during the link is down */ for (; mp; mp = nmp) { nmp = mp->b_next; mp->b_next = NULL; freemsg(mp); } goto x; } ASSERT(dp->nic_state == NIC_STATE_ONLINE); limit = dp->tx_max_packets; for (; limit-- && mp; mp = nmp) { nmp = mp->b_next; mp->b_next = NULL; if (usbgem_send_common(dp, mp, (limit == 0 && nmp) ? 1 : 0)) { mp->b_next = nmp; break; } } #ifdef CONFIG_TX_LIMITER if (mp == mp_head) { /* no packets were sent, descrease allocation limit */ mutex_enter(&dp->txlock); dp->tx_max_packets = max(dp->tx_max_packets - 1, 1); mutex_exit(&dp->txlock); } #endif x: rw_exit(&dp->dev_state_lock); return (mp); } static void usbgem_m_ioctl(void *arg, queue_t *wq, mblk_t *mp) { struct usbgem_dev *dp = arg; DPRINTF(1, (CE_CONT, "!%s: %s: called", ((struct usbgem_dev *)arg)->name, __func__)); rw_enter(&dp->dev_state_lock, RW_READER); usbgem_mac_ioctl((struct usbgem_dev *)arg, wq, mp); rw_exit(&dp->dev_state_lock); } static void usbgem_gld3_init(struct usbgem_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 & USBGEM_VLAN) { macp->m_margin = VTAG_SIZE; } } /* ======================================================================== */ /* * .conf interface */ /* ======================================================================== */ void usbgem_generate_macaddr(struct usbgem_dev *dp, uint8_t *mac) { extern char hw_serial[]; char *hw_serial_p; int i; uint64_t val; uint64_t key; cmn_err(CE_NOTE, "!%s: using temp ether address," " do not use this for long time", dp->name); /* prefer a fixed address for DHCP */ hw_serial_p = &hw_serial[0]; val = stoi(&hw_serial_p); key = 0; for (i = 0; i < USBGEM_NAME_LEN; i++) { if (dp->name[i] == 0) { break; } key ^= dp->name[i]; } key ^= ddi_get_instance(dp->dip); val ^= key << 32; /* generate a local address */ mac[0] = 0x02; mac[1] = (uint8_t)(val >> 32); mac[2] = (uint8_t)(val >> 24); mac[3] = (uint8_t)(val >> 16); mac[4] = (uint8_t)(val >> 8); mac[5] = (uint8_t)val; } boolean_t usbgem_get_mac_addr_conf(struct usbgem_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) { usbgem_generate_macaddr(dp, mac); } 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); ddi_prop_free(valstr); return (B_FALSE); } static void usbgem_read_conf(struct usbgem_dev *dp) { int val; DPRINTF(1, (CE_CONT, "!%s: %s: called", dp->name, __func__)); /* * Get media mode infomation from .conf file */ dp->anadv_autoneg = usbgem_prop_get_int(dp, "adv_autoneg_cap", 1) != 0; dp->anadv_1000fdx = usbgem_prop_get_int(dp, "adv_1000fdx_cap", 1) != 0; dp->anadv_1000hdx = usbgem_prop_get_int(dp, "adv_1000hdx_cap", 1) != 0; dp->anadv_100t4 = usbgem_prop_get_int(dp, "adv_100T4_cap", 1) != 0; dp->anadv_100fdx = usbgem_prop_get_int(dp, "adv_100fdx_cap", 1) != 0; dp->anadv_100hdx = usbgem_prop_get_int(dp, "adv_100hdx_cap", 1) != 0; dp->anadv_10fdx = usbgem_prop_get_int(dp, "adv_10fdx_cap", 1) != 0; dp->anadv_10hdx = usbgem_prop_get_int(dp, "adv_10hdx_cap", 1) != 0; dp->anadv_1000t_ms = usbgem_prop_get_int(dp, "adv_1000t_ms", 0); if ((ddi_prop_exists(DDI_DEV_T_ANY, dp->dip, DDI_PROP_DONTPASS, "full-duplex"))) { dp->full_duplex = usbgem_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 = usbgem_prop_get_int(dp, "speed", 0)) > 0) { dp->anadv_autoneg = B_FALSE; switch (val) { case 1000: dp->speed = USBGEM_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 = USBGEM_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 = USBGEM_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 = usbgem_prop_get_int(dp, "adv_pause", dp->ugc.usbgc_flow_control & 1); val |= usbgem_prop_get_int(dp, "adv_asmpause", BOOLEAN(dp->ugc.usbgc_flow_control & 2)) << 1; 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->ugc.usbgc_flow_control); } dp->anadv_pause = BOOLEAN(val & 1); dp->anadv_asmpause = BOOLEAN(val & 2); dp->mtu = usbgem_prop_get_int(dp, "mtu", dp->mtu); dp->txthr = usbgem_prop_get_int(dp, "txthr", dp->txthr); dp->rxthr = usbgem_prop_get_int(dp, "rxthr", dp->rxthr); dp->txmaxdma = usbgem_prop_get_int(dp, "txmaxdma", dp->txmaxdma); dp->rxmaxdma = usbgem_prop_get_int(dp, "rxmaxdma", dp->rxmaxdma); #ifdef GEM_CONFIG_POLLING dp->poll_pkt_delay = usbgem_prop_get_int(dp, "pkt_delay", dp->poll_pkt_delay); dp->max_poll_interval[GEM_SPD_10] = usbgem_prop_get_int(dp, "max_poll_interval_10", dp->max_poll_interval[GEM_SPD_10]); dp->max_poll_interval[GEM_SPD_100] = usbgem_prop_get_int(dp, "max_poll_interval_100", dp->max_poll_interval[GEM_SPD_100]); dp->max_poll_interval[GEM_SPD_1000] = usbgem_prop_get_int(dp, "max_poll_interval_1000", dp->max_poll_interval[GEM_SPD_1000]); dp->min_poll_interval[GEM_SPD_10] = usbgem_prop_get_int(dp, "min_poll_interval_10", dp->min_poll_interval[GEM_SPD_10]); dp->min_poll_interval[GEM_SPD_100] = usbgem_prop_get_int(dp, "min_poll_interval_100", dp->min_poll_interval[GEM_SPD_100]); dp->min_poll_interval[GEM_SPD_1000] = usbgem_prop_get_int(dp, "min_poll_interval_1000", dp->min_poll_interval[GEM_SPD_1000]); #endif } /* * attach/detatch/usb support */ /* ======================================================================== */ int usbgem_ctrl_out(struct usbgem_dev *dp, uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, void *bp, int size) { mblk_t *data; usb_ctrl_setup_t setup; usb_cr_t completion_reason; usb_cb_flags_t cb_flags; usb_flags_t flags; int i; int ret; DPRINTF(4, (CE_CONT, "!%s: %s " "reqt:0x%02x req:0x%02x val:0x%04x ix:0x%04x len:0x%02x " "bp:0x%p nic_state:%d", dp->name, __func__, reqt, req, val, ix, len, bp, dp->nic_state)); if (dp->mac_state == MAC_STATE_DISCONNECTED) { return (USB_PIPE_ERROR); } data = NULL; if (size > 0) { if ((data = allocb(size, 0)) == NULL) { return (USB_FAILURE); } bcopy(bp, data->b_rptr, size); data->b_wptr = data->b_rptr + size; } setup.bmRequestType = reqt; setup.bRequest = req; setup.wValue = val; setup.wIndex = ix; setup.wLength = len; setup.attrs = 0; /* attributes */ for (i = usbgem_ctrl_retry; i > 0; i--) { completion_reason = 0; cb_flags = 0; ret = usb_pipe_ctrl_xfer_wait(DEFAULT_PIPE(dp), &setup, &data, &completion_reason, &cb_flags, 0); if (ret == USB_SUCCESS) { break; } if (i == 1) { cmn_err(CE_WARN, "!%s: %s failed: " "reqt:0x%x req:0x%x val:0x%x ix:0x%x len:0x%x " "ret:%d cr:%s(%d), cb_flags:0x%x %s", dp->name, __func__, reqt, req, val, ix, len, ret, usb_str_cr(completion_reason), completion_reason, cb_flags, (i > 1) ? "retrying..." : "fatal"); } } if (data != NULL) { freemsg(data); } return (ret); } int usbgem_ctrl_in(struct usbgem_dev *dp, uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, void *bp, int size) { mblk_t *data; usb_ctrl_setup_t setup; usb_cr_t completion_reason; usb_cb_flags_t cb_flags; int i; int ret; int reclen; DPRINTF(4, (CE_CONT, "!%s: %s:" " reqt:0x%02x req:0x%02x val:0x%04x ix:0x%04x len:0x%02x" " bp:x%p mac_state:%d", dp->name, __func__, reqt, req, val, ix, len, bp, dp->mac_state)); if (dp->mac_state == MAC_STATE_DISCONNECTED) { return (USB_PIPE_ERROR); } data = NULL; setup.bmRequestType = reqt; setup.bRequest = req; setup.wValue = val; setup.wIndex = ix; setup.wLength = len; setup.attrs = USB_ATTRS_AUTOCLEARING; /* XXX */ for (i = usbgem_ctrl_retry; i > 0; i--) { completion_reason = 0; cb_flags = 0; ret = usb_pipe_ctrl_xfer_wait(DEFAULT_PIPE(dp), &setup, &data, &completion_reason, &cb_flags, 0); if (ret == USB_SUCCESS) { reclen = msgdsize(data); bcopy(data->b_rptr, bp, min(reclen, size)); break; } if (i == 1) { cmn_err(CE_WARN, "!%s: %s failed: " "reqt:0x%x req:0x%x val:0x%x ix:0x%x len:0x%x " "ret:%d cr:%s(%d) cb_flags:0x%x %s", dp->name, __func__, reqt, req, val, ix, len, ret, usb_str_cr(completion_reason), completion_reason, cb_flags, (i > 1) ? "retrying..." : "fatal"); } } if (data) { freemsg(data); } return (ret); } int usbgem_ctrl_out_val(struct usbgem_dev *dp, uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, uint32_t v) { uint8_t buf[4]; /* convert to little endian from native byte order */ switch (len) { case 4: buf[3] = v >> 24; buf[2] = v >> 16; /* FALLTHROUGH */ case 2: buf[1] = v >> 8; /* FALLTHROUGH */ case 1: buf[0] = v; } return (usbgem_ctrl_out(dp, reqt, req, val, ix, len, buf, len)); } int usbgem_ctrl_in_val(struct usbgem_dev *dp, uint8_t reqt, uint8_t req, uint16_t val, uint16_t ix, uint16_t len, void *valp) { uint8_t buf[4]; uint_t v; int err; #ifdef SANITY bzero(buf, sizeof (buf)); #endif err = usbgem_ctrl_in(dp, reqt, req, val, ix, len, buf, len); if (err == USB_SUCCESS) { v = 0; switch (len) { case 4: v |= buf[3] << 24; v |= buf[2] << 16; /* FALLTHROUGH */ case 2: v |= buf[1] << 8; /* FALLTHROUGH */ case 1: v |= buf[0]; } switch (len) { case 4: *(uint32_t *)valp = v; break; case 2: *(uint16_t *)valp = v; break; case 1: *(uint8_t *)valp = v; break; } } return (err); } /* * Attach / detach / disconnect / reconnect management */ static int usbgem_open_pipes(struct usbgem_dev *dp) { int i; int ret; int ifnum; int alt; usb_client_dev_data_t *reg_data; usb_ep_data_t *ep_tree_node; DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); ifnum = dp->ugc.usbgc_ifnum; alt = dp->ugc.usbgc_alt; ep_tree_node = usb_lookup_ep_data(dp->dip, dp->reg_data, ifnum, alt, 0, USB_EP_ATTR_BULK, USB_EP_DIR_IN); if (ep_tree_node == NULL) { cmn_err(CE_WARN, "!%s: %s: ep_bulkin is NULL", dp->name, __func__); goto err; } dp->ep_bulkin = &ep_tree_node->ep_descr; ep_tree_node = usb_lookup_ep_data(dp->dip, dp->reg_data, ifnum, alt, 0, USB_EP_ATTR_BULK, USB_EP_DIR_OUT); if (ep_tree_node == NULL) { cmn_err(CE_WARN, "!%s: %s: ep_bulkout is NULL", dp->name, __func__); goto err; } dp->ep_bulkout = &ep_tree_node->ep_descr; ep_tree_node = usb_lookup_ep_data(dp->dip, dp->reg_data, ifnum, alt, 0, USB_EP_ATTR_INTR, USB_EP_DIR_IN); if (ep_tree_node) { dp->ep_intr = &ep_tree_node->ep_descr; } else { /* don't care */ DPRINTF(1, (CE_CONT, "!%s: %s: ep_intr is NULL", dp->name, __func__)); dp->ep_intr = NULL; } /* XXX -- no need to open default pipe */ /* open bulk out pipe */ bzero(&dp->policy_bulkout, sizeof (usb_pipe_policy_t)); dp->policy_bulkout.pp_max_async_reqs = 1; if ((ret = usb_pipe_open(dp->dip, dp->ep_bulkout, &dp->policy_bulkout, USB_FLAGS_SLEEP, &dp->bulkout_pipe)) != USB_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: err:%x: failed to open bulk-out pipe", dp->name, __func__, ret); dp->bulkout_pipe = NULL; goto err; } DPRINTF(1, (CE_CONT, "!%s: %s: bulkout_pipe opened successfully", dp->name, __func__)); /* open bulk in pipe */ bzero(&dp->policy_bulkin, sizeof (usb_pipe_policy_t)); dp->policy_bulkin.pp_max_async_reqs = 1; if ((ret = usb_pipe_open(dp->dip, dp->ep_bulkin, &dp->policy_bulkin, USB_FLAGS_SLEEP, &dp->bulkin_pipe)) != USB_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: ret:%x failed to open bulk-in pipe", dp->name, __func__, ret); dp->bulkin_pipe = NULL; goto err; } DPRINTF(1, (CE_CONT, "!%s: %s: bulkin_pipe opened successfully", dp->name, __func__)); if (dp->ep_intr) { /* open interrupt pipe */ bzero(&dp->policy_interrupt, sizeof (usb_pipe_policy_t)); dp->policy_interrupt.pp_max_async_reqs = 1; if ((ret = usb_pipe_open(dp->dip, dp->ep_intr, &dp->policy_interrupt, USB_FLAGS_SLEEP, &dp->intr_pipe)) != USB_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: ret:%x failed to open interrupt pipe", dp->name, __func__, ret); dp->intr_pipe = NULL; goto err; } } DPRINTF(1, (CE_CONT, "!%s: %s: intr_pipe opened successfully", dp->name, __func__)); return (USB_SUCCESS); err: if (dp->bulkin_pipe) { usb_pipe_close(dp->dip, dp->bulkin_pipe, USB_FLAGS_SLEEP, NULL, 0); dp->bulkin_pipe = NULL; } if (dp->bulkout_pipe) { usb_pipe_close(dp->dip, dp->bulkout_pipe, USB_FLAGS_SLEEP, NULL, 0); dp->bulkout_pipe = NULL; } if (dp->intr_pipe) { usb_pipe_close(dp->dip, dp->intr_pipe, USB_FLAGS_SLEEP, NULL, 0); dp->intr_pipe = NULL; } return (USB_FAILURE); } static int usbgem_close_pipes(struct usbgem_dev *dp) { DPRINTF(0, (CE_CONT, "!%s: %s: called", dp->name, __func__)); if (dp->intr_pipe) { usb_pipe_close(dp->dip, dp->intr_pipe, USB_FLAGS_SLEEP, NULL, 0); dp->intr_pipe = NULL; } DPRINTF(1, (CE_CONT, "!%s: %s: 1", dp->name, __func__)); ASSERT(dp->bulkin_pipe); usb_pipe_close(dp->dip, dp->bulkin_pipe, USB_FLAGS_SLEEP, NULL, 0); dp->bulkin_pipe = NULL; DPRINTF(1, (CE_CONT, "!%s: %s: 2", dp->name, __func__)); ASSERT(dp->bulkout_pipe); usb_pipe_close(dp->dip, dp->bulkout_pipe, USB_FLAGS_SLEEP, NULL, 0); dp->bulkout_pipe = NULL; DPRINTF(1, (CE_CONT, "!%s: %s: 3", dp->name, __func__)); return (USB_SUCCESS); } #define FREEZE_GRACEFUL (B_TRUE) #define FREEZE_NO_GRACEFUL (B_FALSE) static int usbgem_freeze_device(struct usbgem_dev *dp, boolean_t graceful) { DPRINTF(0, (CE_NOTE, "!%s: %s: called", dp->name, __func__)); /* stop nic activity */ (void) usbgem_mac_stop(dp, MAC_STATE_DISCONNECTED, graceful); /* * Here we free all memory resource allocated, because it will * cause to panic the system that we free usb_bulk_req objects * during the usb device is disconnected. */ (void) usbgem_free_memory(dp); return (USB_SUCCESS); } static int usbgem_disconnect_cb(dev_info_t *dip) { int ret; struct usbgem_dev *dp; dp = USBGEM_GET_DEV(dip); cmn_err(CE_NOTE, "!%s: the usb device was disconnected (dp=%p)", dp->name, (void *)dp); /* start serialize */ rw_enter(&dp->dev_state_lock, RW_WRITER); ret = usbgem_freeze_device(dp, 0); /* end of serialize */ rw_exit(&dp->dev_state_lock); return (ret); } static int usbgem_recover_device(struct usbgem_dev *dp) { int err; DPRINTF(0, (CE_NOTE, "!%s: %s: called", dp->name, __func__)); err = USB_SUCCESS; /* reinitialize the usb connection */ usbgem_close_pipes(dp); if ((err = usbgem_open_pipes(dp)) != USB_SUCCESS) { goto x; } /* initialize nic state */ dp->mac_state = MAC_STATE_STOPPED; dp->mii_state = MII_STATE_UNKNOWN; /* allocate memory resources again */ if ((err = usbgem_alloc_memory(dp)) != USB_SUCCESS) { goto x; } /* restart nic and recover state */ (void) usbgem_restart_nic(dp); usbgem_mii_init(dp); /* kick potentially stopped house keeping thread */ cv_signal(&dp->link_watcher_wait_cv); x: return (err); } static int usbgem_reconnect_cb(dev_info_t *dip) { int err = USB_SUCCESS; struct usbgem_dev *dp; dp = USBGEM_GET_DEV(dip); DPRINTF(0, (CE_CONT, "!%s: dp=%p", ddi_get_name(dip), dp)); #ifdef notdef /* check device changes after disconnect */ if (usb_check_same_device(dp->dip, NULL, USB_LOG_L2, -1, USB_CHK_BASIC | USB_CHK_CFG, NULL) != USB_SUCCESS) { cmn_err(CE_CONT, "!%s: no or different device installed", dp->name); return (DDI_SUCCESS); } #endif cmn_err(CE_NOTE, "%s: the usb device was reconnected", dp->name); /* start serialize */ rw_enter(&dp->dev_state_lock, RW_WRITER); if (dp->mac_state == MAC_STATE_DISCONNECTED) { err = usbgem_recover_device(dp); } /* end of serialize */ rw_exit(&dp->dev_state_lock); return (err == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE); } int usbgem_suspend(dev_info_t *dip) { int err = USB_SUCCESS; struct usbgem_dev *dp; dp = USBGEM_GET_DEV(dip); DPRINTF(0, (CE_CONT, "!%s: %s: callded", dp->name, __func__)); /* start serialize */ rw_enter(&dp->dev_state_lock, RW_WRITER); if (dp->mac_state == MAC_STATE_DISCONNECTED) { err = usbgem_freeze_device(dp, STOP_GRACEFUL); } /* end of serialize */ rw_exit(&dp->dev_state_lock); return (err == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE); } int usbgem_resume(dev_info_t *dip) { int err = USB_SUCCESS; struct usbgem_dev *dp; dp = USBGEM_GET_DEV(dip); DPRINTF(0, (CE_CONT, "!%s: %s: callded", dp->name, __func__)); #ifdef notdef /* check device changes after disconnect */ if (usb_check_same_device(dp->dip, NULL, USB_LOG_L2, -1, USB_CHK_BASIC | USB_CHK_CFG, NULL) != USB_SUCCESS) { cmn_err(CE_CONT, "!%s: no or different device installed", dp->name); return (DDI_SUCCESS); } #endif /* start serialize */ rw_enter(&dp->dev_state_lock, RW_WRITER); if (dp->mac_state == MAC_STATE_DISCONNECTED) { err = usbgem_recover_device(dp); } /* end of serialize */ rw_exit(&dp->dev_state_lock); return (err == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE); } #define USBGEM_LOCAL_DATA_SIZE(gc) \ (sizeof (struct usbgem_dev) + USBGEM_MCALLOC) struct usbgem_dev * usbgem_do_attach(dev_info_t *dip, struct usbgem_conf *gc, void *lp, int lmsize) { struct usbgem_dev *dp; int i; mac_register_t *macp = NULL; int ret; int unit; int err; unit = ddi_get_instance(dip); DPRINTF(2, (CE_CONT, "!usbgem%d: %s: called", unit, __func__)); /* * Allocate soft data structure */ dp = kmem_zalloc(USBGEM_LOCAL_DATA_SIZE(gc), KM_SLEEP); if (dp == NULL) { return (NULL); } if ((macp = mac_alloc(MAC_VERSION)) == NULL) { cmn_err(CE_WARN, "!gem%d: %s: mac_alloc failed", unit, __func__); return (NULL); } /* link to private area */ dp->private = lp; dp->priv_size = lmsize; dp->mc_list = (struct mcast_addr *)&dp[1]; dp->dip = dip; bcopy(gc->usbgc_name, dp->name, USBGEM_NAME_LEN); /* * register with usb service */ if (usb_client_attach(dip, USBDRV_VERSION, 0) != USB_SUCCESS) { cmn_err(CE_WARN, "%s: %s: usb_client_attach failed", dp->name, __func__); goto err_free_private; } if (usb_get_dev_data(dip, &dp->reg_data, USB_PARSE_LVL_ALL, 0) != USB_SUCCESS) { dp->reg_data = NULL; goto err_unregister_client; } #ifdef USBGEM_DEBUG_LEVEL usb_print_descr_tree(dp->dip, dp->reg_data); #endif if (usbgem_open_pipes(dp) != USB_SUCCESS) { /* failed to open pipes */ cmn_err(CE_WARN, "!%s: %s: failed to open pipes", dp->name, __func__); goto err_unregister_client; } /* * Initialize mutexs and condition variables */ mutex_init(&dp->rxlock, NULL, MUTEX_DRIVER, NULL); mutex_init(&dp->txlock, NULL, MUTEX_DRIVER, NULL); cv_init(&dp->rx_drain_cv, NULL, CV_DRIVER, NULL); cv_init(&dp->tx_drain_cv, NULL, CV_DRIVER, NULL); rw_init(&dp->dev_state_lock, NULL, RW_DRIVER, NULL); mutex_init(&dp->link_watcher_lock, NULL, MUTEX_DRIVER, NULL); cv_init(&dp->link_watcher_wait_cv, NULL, CV_DRIVER, NULL); sema_init(&dp->hal_op_lock, 1, NULL, SEMA_DRIVER, NULL); sema_init(&dp->rxfilter_lock, 1, NULL, SEMA_DRIVER, NULL); /* * Initialize configuration */ dp->ugc = *gc; dp->mtu = ETHERMTU; dp->rxmode = 0; dp->speed = USBGEM_SPD_10; /* default is 10Mbps */ dp->full_duplex = B_FALSE; /* default is half */ dp->flow_control = FLOW_CONTROL_NONE; dp->nic_state = NIC_STATE_STOPPED; dp->mac_state = MAC_STATE_STOPPED; dp->mii_state = MII_STATE_UNKNOWN; /* performance tuning parameters */ dp->txthr = ETHERMAX; /* tx fifo threshoold */ dp->txmaxdma = 16*4; /* tx max dma burst size */ dp->rxthr = 128; /* rx fifo threshoold */ dp->rxmaxdma = 16*4; /* rx max dma burst size */ /* * Get media mode infomation from .conf file */ usbgem_read_conf(dp); /* rx_buf_len depend on MTU */ dp->rx_buf_len = MAXPKTBUF(dp) + dp->ugc.usbgc_rx_header_len; /* * Reset the chip */ if (usbgem_hal_reset_chip(dp) != USB_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: failed to reset the usb device", dp->name, __func__); goto err_destroy_locks; } /* * HW dependant paremeter initialization */ if (usbgem_hal_attach_chip(dp) != USB_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: failed to attach the usb device", dp->name, __func__); goto err_destroy_locks; } /* allocate resources */ if (usbgem_alloc_memory(dp) != USB_SUCCESS) { goto err_destroy_locks; } DPRINTF(0, (CE_CONT, "!%s: %02x:%02x:%02x:%02x:%02x:%02x", dp->name, 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; /* pre-calculated tx timeout in second for performance */ dp->bulkout_timeout = dp->ugc.usbgc_tx_timeout / drv_usectohz(1000*1000); usbgem_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; dp->mii_status_ro = 0; dp->mii_xstatus_ro = 0; if (usbgem_mii_probe(dp) != USB_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: mii_probe failed", dp->name, __func__); goto err_free_memory; } /* 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); if (usbgem_mii_init(dp) != USB_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: mii_init failed", dp->name, __func__); goto err_free_memory; } /* * 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 (usb_register_hotplug_cbs(dip, usbgem_suspend, usbgem_resume) != USB_SUCCESS) { cmn_err(CE_WARN, "!%s: %s: failed to register hotplug cbs", dp->name, __func__); goto err_unregister_gld; } /* reset mii and start mii link watcher */ if (usbgem_mii_start(dp) != USB_SUCCESS) { goto err_unregister_hotplug; } /* start tx watchdow watcher */ if (usbgem_tx_watcher_start(dp)) { goto err_usbgem_mii_stop; } ddi_set_driver_private(dip, (caddr_t)dp); DPRINTF(2, (CE_CONT, "!%s: %s: return: success", dp->name, __func__)); return (dp); err_usbgem_mii_stop: usbgem_mii_stop(dp); err_unregister_hotplug: usb_unregister_hotplug_cbs(dip); err_unregister_gld: mac_unregister(dp->mh); err_release_stats: err_free_memory: usbgem_free_memory(dp); err_destroy_locks: cv_destroy(&dp->tx_drain_cv); cv_destroy(&dp->rx_drain_cv); mutex_destroy(&dp->txlock); mutex_destroy(&dp->rxlock); rw_destroy(&dp->dev_state_lock); mutex_destroy(&dp->link_watcher_lock); cv_destroy(&dp->link_watcher_wait_cv); sema_destroy(&dp->hal_op_lock); sema_destroy(&dp->rxfilter_lock); err_close_pipes: (void) usbgem_close_pipes(dp); err_unregister_client: usb_client_detach(dp->dip, dp->reg_data); err_free_private: if (macp) { mac_free(macp); } kmem_free((caddr_t)dp, USBGEM_LOCAL_DATA_SIZE(gc)); return (NULL); } int usbgem_do_detach(dev_info_t *dip) { struct usbgem_dev *dp; dp = USBGEM_GET_DEV(dip); /* unregister with gld v3 */ if (mac_unregister(dp->mh) != DDI_SUCCESS) { return (DDI_FAILURE); } /* unregister with hotplug service */ usb_unregister_hotplug_cbs(dip); /* stop tx watchdog watcher */ usbgem_tx_watcher_stop(dp); /* stop the link manager */ usbgem_mii_stop(dp); /* unregister with usb service */ (void) usbgem_free_memory(dp); (void) usbgem_close_pipes(dp); usb_client_detach(dp->dip, dp->reg_data); dp->reg_data = NULL; /* release locks and condition variables */ mutex_destroy(&dp->txlock); mutex_destroy(&dp->rxlock); cv_destroy(&dp->tx_drain_cv); cv_destroy(&dp->rx_drain_cv); rw_destroy(&dp->dev_state_lock); mutex_destroy(&dp->link_watcher_lock); cv_destroy(&dp->link_watcher_wait_cv); sema_destroy(&dp->hal_op_lock); sema_destroy(&dp->rxfilter_lock); /* release basic memory resources */ kmem_free((caddr_t)(dp->private), dp->priv_size); kmem_free((caddr_t)dp, USBGEM_LOCAL_DATA_SIZE(&dp->ugc)); DPRINTF(2, (CE_CONT, "!%s: %s: return: success", ddi_driver_name(dip), __func__)); return (DDI_SUCCESS); } int usbgem_mod_init(struct dev_ops *dop, char *name) { major_t major; major = ddi_name_to_major(name); if (major == DDI_MAJOR_T_NONE) { return (DDI_FAILURE); } mac_init_ops(dop, name); return (DDI_SUCCESS); } void usbgem_mod_fini(struct dev_ops *dop) { mac_fini_ops(dop); } int usbgem_quiesce(dev_info_t *dip) { struct usbgem_dev *dp; dp = USBGEM_GET_DEV(dip); ASSERT(dp != NULL); if (dp->mac_state != MAC_STATE_DISCONNECTED && dp->mac_state != MAC_STATE_STOPPED) { if (usbgem_hal_stop_chip(dp) != USB_SUCCESS) { (void) usbgem_hal_reset_chip(dp); } } /* devo_quiesce() must return DDI_SUCCESS always */ return (DDI_SUCCESS); }