/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2024 Oxide Computer Company */ /* * Intel I225/226 Ethernet Driver. This is the same MAC that is found in the * e1000 and igb drivers, but Intel decided it would be a different driver and * so here we are. */ #include #include "igc.h" typedef struct { enum igc_media_type imm_phy; uint16_t imm_speed; mac_ether_media_t imm_media; } igc_media_map_t; static const igc_media_map_t igc_media_map[] = { { igc_media_type_copper, SPEED_10, ETHER_MEDIA_10BASE_T }, { igc_media_type_copper, SPEED_100, ETHER_MEDIA_100BASE_TX }, { igc_media_type_copper, SPEED_1000, ETHER_MEDIA_1000BASE_T }, { igc_media_type_copper, SPEED_2500, ETHER_MEDIA_2500BASE_T }, }; static mac_ether_media_t igc_link_to_media(igc_t *igc) { switch (igc->igc_link_state) { case LINK_STATE_UP: break; case LINK_STATE_DOWN: return (ETHER_MEDIA_NONE); default: return (ETHER_MEDIA_UNKNOWN); } for (size_t i = 0; i < ARRAY_SIZE(igc_media_map); i++) { const igc_media_map_t *map = &igc_media_map[i]; if (igc->igc_hw.phy.media_type == map->imm_phy && igc->igc_link_speed == map->imm_speed) { return (map->imm_media); } } return (ETHER_MEDIA_UNKNOWN); } /* * The following stats are skipped because there is no good way to get it from * hardware or we don't know how to perform such a mapping: * * - MAC_STAT_UNKNOWNS * - MAC_STAT_UNDERFLOWS * - MAC_STAT_OVERFLOWS * - ETHER_STAT_SQE_ERRORS * - ETHER_STAT_MACRCV_ERRORS */ static int igc_m_getstat(void *drv, uint_t stat, uint64_t *valp) { igc_t *igc = drv; igc_stats_t *stats = &igc->igc_stats; int ret = 0; uint32_t an_adv; mutex_enter(&igc->igc_lock); an_adv = igc->igc_hw.phy.autoneg_advertised; switch (stat) { /* MIB-II stats (RFC 1213 and RFC 1573) */ case MAC_STAT_IFSPEED: *valp = (uint64_t)igc->igc_link_speed * 1000000; break; case MAC_STAT_MULTIRCV: stats->is_mprc.value.ui64 += igc_read32(igc, IGC_MPRC); *valp = stats->is_mprc.value.ui64; break; case MAC_STAT_BRDCSTRCV: stats->is_bprc.value.ui64 += igc_read32(igc, IGC_BPRC); *valp = stats->is_bprc.value.ui64; break; case MAC_STAT_MULTIXMT: stats->is_mptc.value.ui64 += igc_read32(igc, IGC_MPTC); *valp = stats->is_mptc.value.ui64; break; case MAC_STAT_BRDCSTXMT: stats->is_bptc.value.ui64 += igc_read32(igc, IGC_BPTC); *valp = stats->is_bptc.value.ui64; break; case MAC_STAT_NORCVBUF: stats->is_rnbc.value.ui64 += igc_read32(igc, IGC_RNBC); *valp = stats->is_rnbc.value.ui64; break; case MAC_STAT_IERRORS: stats->is_crcerrs.value.ui64 += igc_read32(igc, IGC_CRCERRS); stats->is_rlec.value.ui64 += igc_read32(igc, IGC_RLEC); stats->is_algnerrc.value.ui64 += igc_read32(igc, IGC_ALGNERRC); *valp = stats->is_crcerrs.value.ui64 + stats->is_rlec.value.ui64 + stats->is_algnerrc.value.ui64; break; case MAC_STAT_OERRORS: stats->is_ecol.value.ui64 += igc_read32(igc, IGC_ECOL); stats->is_latecol.value.ui64 += igc_read32(igc, IGC_LATECOL); *valp = stats->is_ecol.value.ui64 + stats->is_latecol.value.ui64; break; case MAC_STAT_COLLISIONS: stats->is_colc.value.ui64 += igc_read32(igc, IGC_COLC); *valp = stats->is_colc.value.ui64; break; case MAC_STAT_RBYTES: igc_stats_update_u64(igc, &stats->is_tor, IGC_TORL); *valp = stats->is_tor.value.ui64; break; case MAC_STAT_IPACKETS: stats->is_tpr.value.ui64 += igc_read32(igc, IGC_TPR); *valp = stats->is_tpr.value.ui64; break; case MAC_STAT_OBYTES: igc_stats_update_u64(igc, &stats->is_tor, IGC_TOTL); *valp = stats->is_tot.value.ui64; break; case MAC_STAT_OPACKETS: stats->is_tpt.value.ui64 += igc_read32(igc, IGC_TPT); *valp = stats->is_tpt.value.ui64; break; case MAC_STAT_UNDERFLOWS: stats->is_ruc.value.ui64 += igc_read32(igc, IGC_RUC); *valp = stats->is_ruc.value.ui64; break; case MAC_STAT_OVERFLOWS: stats->is_roc.value.ui64 += igc_read32(igc, IGC_ROC); *valp = stats->is_roc.value.ui64; break; /* RFC 1643 stats */ case ETHER_STAT_ALIGN_ERRORS: stats->is_algnerrc.value.ui64 += igc_read32(igc, IGC_ALGNERRC); *valp = stats->is_algnerrc.value.ui64; break; case ETHER_STAT_FCS_ERRORS: stats->is_crcerrs.value.ui64 += igc_read32(igc, IGC_CRCERRS); *valp = stats->is_crcerrs.value.ui64; break; case ETHER_STAT_FIRST_COLLISIONS: stats->is_scc.value.ui64 += igc_read32(igc, IGC_SCC); *valp = stats->is_scc.value.ui64; break; case ETHER_STAT_MULTI_COLLISIONS: stats->is_mcc.value.ui64 += igc_read32(igc, IGC_MCC); *valp = stats->is_mcc.value.ui64; break; case ETHER_STAT_DEFER_XMTS: stats->is_dc.value.ui64 += igc_read32(igc, IGC_DC); *valp = stats->is_dc.value.ui64; break; case ETHER_STAT_TX_LATE_COLLISIONS: stats->is_latecol.value.ui64 += igc_read32(igc, IGC_LATECOL); *valp = stats->is_latecol.value.ui64; break; case ETHER_STAT_EX_COLLISIONS: stats->is_ecol.value.ui64 += igc_read32(igc, IGC_ECOL); *valp = stats->is_ecol.value.ui64; break; case ETHER_STAT_MACXMT_ERRORS: stats->is_ecol.value.ui64 += igc_read32(igc, IGC_ECOL); *valp = stats->is_ecol.value.ui64; break; case ETHER_STAT_CARRIER_ERRORS: stats->is_htdpmc.value.ui64 += igc_read32(igc, IGC_HTDPMC); *valp = stats->is_htdpmc.value.ui64; break; case ETHER_STAT_TOOLONG_ERRORS: stats->is_roc.value.ui64 += igc_read32(igc, IGC_ROC); *valp = stats->is_roc.value.ui64; break; /* MII/GMII stats */ case ETHER_STAT_XCVR_ADDR: *valp = igc->igc_hw.phy.addr; break; case ETHER_STAT_XCVR_ID: *valp = igc->igc_hw.phy.id | igc->igc_hw.phy.revision; break; case ETHER_STAT_XCVR_INUSE: *valp = igc_link_to_media(igc); break; case ETHER_STAT_CAP_2500FDX: case ETHER_STAT_CAP_1000FDX: case ETHER_STAT_CAP_100FDX: case ETHER_STAT_CAP_100HDX: case ETHER_STAT_CAP_10FDX: case ETHER_STAT_CAP_10HDX: case ETHER_STAT_CAP_ASMPAUSE: case ETHER_STAT_CAP_PAUSE: case ETHER_STAT_CAP_AUTONEG: /* * These are all about what the device is capable of and every * device is capable of this that we support right now. */ *valp = 1; break; case ETHER_STAT_ADV_CAP_2500FDX: *valp = (an_adv & ADVERTISE_2500_FULL) != 0; break; case ETHER_STAT_ADV_CAP_1000FDX: *valp = (an_adv & ADVERTISE_1000_FULL) != 0; break; case ETHER_STAT_ADV_CAP_100FDX: *valp = (an_adv & ADVERTISE_100_FULL) != 0; break; case ETHER_STAT_ADV_CAP_100HDX: *valp = (an_adv & ADVERTISE_100_HALF) != 0; break; case ETHER_STAT_ADV_CAP_10FDX: *valp = (an_adv & ADVERTISE_10_FULL) != 0; break; case ETHER_STAT_ADV_CAP_10HDX: *valp = (an_adv & ADVERTISE_10_HALF) != 0; break; case ETHER_STAT_ADV_CAP_ASMPAUSE: *valp = (igc->igc_phy_an_adv & NWAY_AR_ASM_DIR) != 0; break; case ETHER_STAT_ADV_CAP_PAUSE: *valp = (igc->igc_phy_an_adv & NWAY_AR_PAUSE) != 0; break; case ETHER_STAT_ADV_CAP_AUTONEG: *valp = igc->igc_hw.mac.autoneg; break; case ETHER_STAT_LP_CAP_2500FDX: *valp = (igc->igc_phy_mmd_sts & MMD_AN_STS1_LP_2P5T_CAP) != 0; break; case ETHER_STAT_LP_CAP_1000FDX: *valp = (igc->igc_phy_1000t_status & SR_1000T_LP_FD_CAPS) != 0; break; case ETHER_STAT_LP_CAP_100FDX: *valp = (igc->igc_phy_lp & NWAY_LPAR_100TX_FD_CAPS) != 0; break; case ETHER_STAT_LP_CAP_100HDX: *valp = (igc->igc_phy_lp & NWAY_LPAR_100TX_HD_CAPS) != 0; break; case ETHER_STAT_LP_CAP_10FDX: *valp = (igc->igc_phy_lp & NWAY_LPAR_10T_FD_CAPS) != 0; break; case ETHER_STAT_LP_CAP_10HDX: *valp = (igc->igc_phy_lp & NWAY_LPAR_10T_HD_CAPS) != 0; break; case ETHER_STAT_LP_CAP_ASMPAUSE: *valp = (igc->igc_phy_lp & NWAY_AR_ASM_DIR) != 0; break; case ETHER_STAT_LP_CAP_PAUSE: *valp = (igc->igc_phy_lp & NWAY_LPAR_PAUSE) != 0; break; case ETHER_STAT_LP_CAP_AUTONEG: *valp = (igc->igc_phy_ext_status & NWAY_ER_LP_NWAY_CAPS) != 0; break; case ETHER_STAT_LINK_ASMPAUSE: *valp = (igc->igc_hw.fc.current_mode == igc_fc_full || igc->igc_hw.fc.current_mode == igc_fc_rx_pause); break; case ETHER_STAT_LINK_PAUSE: *valp = (igc->igc_hw.fc.current_mode == igc_fc_full || igc->igc_hw.fc.current_mode == igc_fc_tx_pause); break; case ETHER_STAT_LINK_AUTONEG: *valp = igc->igc_hw.mac.autoneg; break; case ETHER_STAT_LINK_DUPLEX: *valp = igc->igc_link_duplex; break; case ETHER_STAT_CAP_REMFAULT: *valp = 1; break; case ETHER_STAT_ADV_REMFAULT: *valp = (igc->igc_phy_an_adv & NWAY_AR_REMOTE_FAULT) != 0; break; case ETHER_STAT_LP_REMFAULT: *valp = (igc->igc_phy_lp & NWAY_LPAR_REMOTE_FAULT) != 0; break; case ETHER_STAT_TOOSHORT_ERRORS: stats->is_ruc.value.ui64 += igc_read32(igc, IGC_RUC); *valp = stats->is_ruc.value.ui64; break; case ETHER_STAT_JABBER_ERRORS: stats->is_rjc.value.ui64 += igc_read32(igc, IGC_RJC); *valp = stats->is_rjc.value.ui64; break; /* * Unsupported speeds. */ case ETHER_STAT_CAP_100T4: case ETHER_STAT_CAP_1000HDX: case ETHER_STAT_CAP_10GFDX: case ETHER_STAT_CAP_40GFDX: case ETHER_STAT_CAP_100GFDX: case ETHER_STAT_CAP_5000FDX: case ETHER_STAT_CAP_25GFDX: case ETHER_STAT_CAP_50GFDX: case ETHER_STAT_CAP_200GFDX: case ETHER_STAT_CAP_400GFDX: case ETHER_STAT_ADV_CAP_100T4: case ETHER_STAT_ADV_CAP_1000HDX: case ETHER_STAT_ADV_CAP_10GFDX: case ETHER_STAT_ADV_CAP_40GFDX: case ETHER_STAT_ADV_CAP_100GFDX: case ETHER_STAT_ADV_CAP_5000FDX: case ETHER_STAT_ADV_CAP_25GFDX: case ETHER_STAT_ADV_CAP_50GFDX: case ETHER_STAT_ADV_CAP_200GFDX: case ETHER_STAT_ADV_CAP_400GFDX: *valp = 0; break; /* * These are values that aren't supported by igc(4D); however, some of * the MII registers can be used to answer this based on the * MultiGBASE-T spec (and others). */ case ETHER_STAT_LP_CAP_1000HDX: *valp = (igc->igc_phy_1000t_status & SR_1000T_LP_HD_CAPS) != 0; break; case ETHER_STAT_LP_CAP_100T4: *valp = (igc->igc_phy_lp & NWAY_LPAR_100T4_CAPS) != 0; break; case ETHER_STAT_LP_CAP_10GFDX: *valp = (igc->igc_phy_mmd_sts & MMD_AN_STS1_LP_10T_CAP) != 0; break; case ETHER_STAT_LP_CAP_40GFDX: *valp = (igc->igc_phy_mmd_sts & MMD_AN_STS1_LP_40T_CAP) != 0; break; case ETHER_STAT_LP_CAP_5000FDX: *valp = (igc->igc_phy_mmd_sts & MMD_AN_STS1_LP_5T_CAP) != 0; break; case ETHER_STAT_LP_CAP_25GFDX: *valp = (igc->igc_phy_mmd_sts & MMD_AN_STS1_LP_25T_CAP) != 0; break; case ETHER_STAT_LP_CAP_50GFDX: case ETHER_STAT_LP_CAP_100GFDX: case ETHER_STAT_LP_CAP_200GFDX: case ETHER_STAT_LP_CAP_400GFDX: *valp = 0; break; default: ret = ENOTSUP; } mutex_exit(&igc->igc_lock); return (ret); } static void igc_m_stop(void *drv) { igc_t *igc = drv; igc_hw_intr_disable(igc); (void) igc_reset_hw(&igc->igc_hw); igc_rx_drain(igc); igc_rx_data_free(igc); igc_tx_data_free(igc); /* * Now that we're fully stopped, remove all of our state tracking. */ mutex_enter(&igc->igc_lock); igc->igc_attach &= ~IGC_ATTACH_TX_DATA; igc->igc_attach &= ~IGC_ATTACH_RX_DATA; igc->igc_attach &= ~IGC_ATTACH_MAC_START; mutex_exit(&igc->igc_lock); } static int igc_m_start(void *drv) { int ret; igc_t *igc = drv; mutex_enter(&igc->igc_lock); igc->igc_attach |= IGC_ATTACH_MAC_START; mutex_exit(&igc->igc_lock); /* * Ensure that the phy is powerd on. */ igc_power_up_phy(&igc->igc_hw); if (!igc_rx_data_alloc(igc)) { ret = ENOMEM; goto cleanup; } mutex_enter(&igc->igc_lock); igc->igc_attach |= IGC_ATTACH_RX_DATA; mutex_exit(&igc->igc_lock); if (!igc_tx_data_alloc(igc)) { ret = ENOMEM; goto cleanup; } mutex_enter(&igc->igc_lock); igc->igc_attach |= IGC_ATTACH_TX_DATA; mutex_exit(&igc->igc_lock); if (!igc_hw_common_init(igc)) { ret = EIO; goto cleanup; } /* * The above hardware reset ensures that the latest requested link * properties are set and that the packet sizes and related are * programmed. Now we must go through and program the ring information * into the hardware and enable interrupts. Once that's done we're good * to go. */ igc_rx_hw_init(igc); igc_tx_hw_init(igc); igc_hw_intr_enable(igc); return (0); cleanup: mutex_enter(&igc->igc_lock); if ((igc->igc_attach & IGC_ATTACH_TX_DATA) != 0) { igc_tx_data_free(igc); igc->igc_attach &= ~IGC_ATTACH_TX_DATA; } if ((igc->igc_attach & IGC_ATTACH_RX_DATA) != 0) { igc_rx_data_free(igc); igc->igc_attach &= ~IGC_ATTACH_RX_DATA; } igc->igc_attach &= ~IGC_ATTACH_MAC_START; mutex_exit(&igc->igc_lock); return (ret); } static int igc_m_setpromisc(void *drv, boolean_t en) { igc_t *igc = drv; uint32_t reg; mutex_enter(&igc->igc_lock); reg = igc_read32(igc, IGC_RCTL); if (en) { reg |= IGC_RCTL_UPE | IGC_RCTL_MPE; igc->igc_promisc = true; } else { reg &= ~(IGC_RCTL_UPE | IGC_RCTL_MPE); igc->igc_promisc = false; } igc_write32(igc, IGC_RCTL, reg); mutex_exit(&igc->igc_lock); return (0); } static int igc_m_multicast(void *drv, boolean_t add, const uint8_t *mac) { int ret = 0; igc_t *igc = drv; if ((mac[0] & 0x01) == 0) { return (EINVAL); } mutex_enter(&igc->igc_lock); if (add) { bool space = false; for (uint16_t i = 0; i < igc->igc_nmcast; i++) { if (igc->igc_mcast[i].ia_valid) continue; bcopy(mac, igc->igc_mcast[i].ia_mac, ETHERADDRL); igc->igc_mcast[i].ia_valid = true; space = true; break; } if (!space) { ret = ENOSPC; } } else { bool found = false; for (uint16_t i = 0; i < igc->igc_nmcast; i++) { if (!igc->igc_mcast[i].ia_valid || bcmp(mac, igc->igc_mcast[i].ia_mac, ETHERADDRL) != 0) { continue; } bzero(igc->igc_mcast[i].ia_mac, ETHERADDRL); igc->igc_mcast[i].ia_valid = false; found = true; break; } if (!found) { ret = ENOENT; } } igc_multicast_sync(igc); mutex_exit(&igc->igc_lock); return (ret); } static int igc_group_add_mac(void *gr_drv, const uint8_t *mac) { igc_t *igc = gr_drv; ASSERT3U(mac[0] & 0x01, ==, 0); mutex_enter(&igc->igc_lock); for (uint16_t i = 0; i < igc->igc_nucast; i++) { int ret; if (igc->igc_ucast[i].ia_valid) continue; bcopy(mac, igc->igc_ucast[i].ia_mac, ETHERADDRL); igc->igc_ucast[i].ia_valid = true; ret = igc_rar_set(&igc->igc_hw, igc->igc_ucast[i].ia_mac, i); VERIFY3S(ret, ==, IGC_SUCCESS); mutex_exit(&igc->igc_lock); return (0); } mutex_exit(&igc->igc_lock); return (ENOSPC); } static int igc_group_rem_mac(void *gr_drv, const uint8_t *mac) { igc_t *igc = gr_drv; ASSERT3U(mac[0] & 0x01, ==, 0); mutex_enter(&igc->igc_lock); for (uint16_t i = 0; i < igc->igc_nucast; i++) { int ret; if (!igc->igc_ucast[i].ia_valid || bcmp(mac, igc->igc_ucast[i].ia_mac, ETHERADDRL) != 0) { continue; } bzero(igc->igc_ucast[i].ia_mac, ETHERADDRL); igc->igc_ucast[i].ia_valid = false; ret = igc_rar_set(&igc->igc_hw, igc->igc_ucast[i].ia_mac, i); VERIFY3S(ret, ==, IGC_SUCCESS); mutex_exit(&igc->igc_lock); return (0); } mutex_exit(&igc->igc_lock); return (ENOENT); } int igc_tx_ring_stat(mac_ring_driver_t rh, uint_t stat, uint64_t *val) { igc_tx_ring_t *tx_ring = (igc_tx_ring_t *)rh; switch (stat) { case MAC_STAT_OBYTES: *val = tx_ring->itr_stat.its_obytes.value.ui64; break; case MAC_STAT_OPACKETS: *val = tx_ring->itr_stat.its_opackets.value.ui64; break; default: *val = 0; return (ENOTSUP); } return (0); } int igc_rx_ring_start(mac_ring_driver_t rh, uint64_t gen) { igc_rx_ring_t *rx_ring = (igc_rx_ring_t *)rh; mutex_enter(&rx_ring->irr_lock); rx_ring->irr_gen = gen; mutex_exit(&rx_ring->irr_lock); return (0); } mblk_t * igc_rx_ring_poll(void *drv, int nbytes) { mblk_t *mp; igc_rx_ring_t *ring = drv; ASSERT3S(nbytes, >, 0); if (nbytes == 0) { return (NULL); } mutex_enter(&ring->irr_lock); mp = igc_ring_rx(ring, nbytes); mutex_exit(&ring->irr_lock); return (mp); } int igc_rx_ring_stat(mac_ring_driver_t rh, uint_t stat, uint64_t *val) { igc_rx_ring_t *rx_ring = (igc_rx_ring_t *)rh; switch (stat) { case MAC_STAT_RBYTES: *val = rx_ring->irr_stat.irs_rbytes.value.ui64; break; case MAC_STAT_IPACKETS: *val = rx_ring->irr_stat.irs_ipackets.value.ui64; break; default: *val = 0; return (ENOTSUP); } return (0); } int igc_rx_ring_intr_enable(mac_intr_handle_t ih) { igc_rx_ring_t *ring = (igc_rx_ring_t *)ih; igc_t *igc = ring->irr_igc; /* * Disabling a ring requires us updating shared device registers. So we * use the igc_lock to protect that. We also grab the irr_lock, so we * can synchronize with the I/O path. */ mutex_enter(&igc->igc_lock); mutex_enter(&ring->irr_lock); ring->irr_flags &= ~IGC_RXR_F_POLL; mutex_exit(&ring->irr_lock); /* * Re-enable interrupts. We update our EIMS value and then update both * the EIMS and EIAC. We update the whole set with the EIMS just to * simplify things. */ igc->igc_eims |= 1 << ring->irr_intr_idx; igc_write32(igc, IGC_EIMS, igc->igc_eims); igc_write32(igc, IGC_EIAC, igc->igc_eims); mutex_exit(&igc->igc_lock); return (0); } int igc_rx_ring_intr_disable(mac_intr_handle_t ih) { igc_rx_ring_t *ring = (igc_rx_ring_t *)ih; igc_t *igc = ring->irr_igc; /* * Disabling a ring requires us updating shared device registers. So we * use the igc_lock to protect that. We also grab the irr_lock, so we * can synchronize with the I/O path. */ mutex_enter(&igc->igc_lock); mutex_enter(&ring->irr_lock); ring->irr_flags |= IGC_RXR_F_POLL; mutex_exit(&ring->irr_lock); /* * Writing to the EIMC register masks off interrupts for this. We also * clear this from EIAC as a means of making sure it also won't * retrigger. We remove this queue from our global tracking set of what * the eims value should be to simplify tracking. */ igc_write32(igc, IGC_EIMC, 1 << ring->irr_intr_idx); igc->igc_eims &= ~ (1 << ring->irr_intr_idx); igc_write32(igc, IGC_EIAC, igc->igc_eims); mutex_exit(&igc->igc_lock); return (0); } static void igc_fill_tx_ring(void *arg, mac_ring_type_t rtype, const int group_idx, const int ring_idx, mac_ring_info_t *infop, mac_ring_handle_t rh) { igc_t *igc = arg; igc_tx_ring_t *ring; ASSERT3S(group_idx, ==, -1); ASSERT3S(ring_idx, <, igc->igc_ntx_rings); ring = &igc->igc_tx_rings[ring_idx]; ring->itr_rh = rh; infop->mri_driver = (mac_ring_driver_t)ring; infop->mri_start = NULL; infop->mri_stop = NULL; infop->mri_tx = igc_ring_tx; infop->mri_stat = igc_tx_ring_stat; if (igc->igc_intr_type == DDI_INTR_TYPE_MSIX) { infop->mri_intr.mi_ddi_handle = igc->igc_intr_handles[ring->itr_intr_idx]; } } static void igc_fill_rx_ring(void *arg, mac_ring_type_t rtype, const int group_idx, const int ring_idx, mac_ring_info_t *infop, mac_ring_handle_t rh) { igc_t *igc = arg; igc_rx_ring_t *ring; ASSERT3S(group_idx, ==, 0); ASSERT3S(ring_idx, <, igc->igc_nrx_rings); ring = &igc->igc_rx_rings[ring_idx]; ring->irr_rh = rh; infop->mri_driver = (mac_ring_driver_t)ring; infop->mri_start = igc_rx_ring_start; infop->mri_stop = NULL; infop->mri_poll = igc_rx_ring_poll; infop->mri_stat = igc_rx_ring_stat; infop->mri_intr.mi_handle = (mac_intr_handle_t)ring; infop->mri_intr.mi_enable = igc_rx_ring_intr_enable; infop->mri_intr.mi_disable = igc_rx_ring_intr_disable; if (igc->igc_intr_type == DDI_INTR_TYPE_MSIX) { infop->mri_intr.mi_ddi_handle = igc->igc_intr_handles[ring->irr_intr_idx]; } } static void igc_fill_rx_group(void *arg, mac_ring_type_t rtype, const int idx, mac_group_info_t *infop, mac_group_handle_t gh) { igc_t *igc = arg; if (rtype != MAC_RING_TYPE_RX) { return; } igc->igc_rxg_hdl = gh; infop->mgi_driver = (mac_group_driver_t)igc; infop->mgi_start = NULL; infop->mgi_stop = NULL; infop->mgi_addmac = igc_group_add_mac; infop->mgi_remmac = igc_group_rem_mac; infop->mgi_count = igc->igc_nrx_rings; } static int igc_led_set(void *drv, mac_led_mode_t mode, uint_t flags) { igc_t *igc = drv; uint32_t led; if (flags != 0) { return (EINVAL); } switch (mode) { case MAC_LED_DEFAULT: led = igc->igc_ledctl; break; case MAC_LED_IDENT: led = igc->igc_ledctl_blink; break; case MAC_LED_OFF: led = igc->igc_ledctl_off; break; case MAC_LED_ON: led = igc->igc_ledctl_on; break; default: return (ENOTSUP); } mutex_enter(&igc->igc_lock); igc_write32(igc, IGC_LEDCTL, led); igc->igc_led_mode = mode; mutex_exit(&igc->igc_lock); return (0); } static boolean_t igc_m_getcapab(void *drv, mac_capab_t capab, void *data) { igc_t *igc = drv; mac_capab_rings_t *rings; mac_capab_led_t *led; uint32_t *cksump; switch (capab) { case MAC_CAPAB_RINGS: rings = data; rings->mr_group_type = MAC_GROUP_TYPE_STATIC; switch (rings->mr_type) { case MAC_RING_TYPE_TX: rings->mr_gnum = 0; rings->mr_rnum = igc->igc_ntx_rings; rings->mr_rget = igc_fill_tx_ring; rings->mr_gget = NULL; rings->mr_gaddring = NULL; rings->mr_gremring = NULL; break; case MAC_RING_TYPE_RX: rings->mr_gnum = 1; rings->mr_rnum = igc->igc_nrx_rings; rings->mr_rget = igc_fill_rx_ring; rings->mr_gget = igc_fill_rx_group; rings->mr_gaddring = NULL; rings->mr_gremring = NULL; break; default: return (B_FALSE); } break; case MAC_CAPAB_HCKSUM: cksump = data; /* * The hardware supports computing the full checksum on receive, * but on transmit needs the partial checksum pre-computed. */ *cksump = HCKSUM_INET_PARTIAL | HCKSUM_IPHDRCKSUM; break; case MAC_CAPAB_LED: led = data; led->mcl_flags = 0; led->mcl_modes = MAC_LED_DEFAULT | MAC_LED_OFF | MAC_LED_ON | MAC_LED_IDENT; led->mcl_set = igc_led_set; return (B_TRUE); default: return (B_FALSE); } return (B_TRUE); } void igc_m_propinfo(void *drv, const char *name, mac_prop_id_t prop, mac_prop_info_handle_t prh) { igc_t *igc = drv; switch (prop) { case MAC_PROP_DUPLEX: case MAC_PROP_SPEED: mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); break; case MAC_PROP_AUTONEG: mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); mac_prop_info_set_default_uint8(prh, 1); break; case MAC_PROP_MTU: mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW); mac_prop_info_set_range_uint32(prh, ETHERMIN, igc->igc_limits.il_max_mtu); mac_prop_info_set_default_uint32(prh, ETHERMTU); break; case MAC_PROP_FLOWCTRL: mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW); mac_prop_info_set_default_link_flowctrl(prh, LINK_FLOWCTRL_BI); break; /* * Right now, all igc devices support the same set of speeds and we * attempt to advertise them all. */ case MAC_PROP_ADV_10HDX_CAP: case MAC_PROP_ADV_10FDX_CAP: case MAC_PROP_ADV_100HDX_CAP: case MAC_PROP_ADV_100FDX_CAP: case MAC_PROP_ADV_1000FDX_CAP: case MAC_PROP_ADV_2500FDX_CAP: mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); mac_prop_info_set_default_uint8(prh, 1); break; case MAC_PROP_EN_10HDX_CAP: case MAC_PROP_EN_10FDX_CAP: case MAC_PROP_EN_100HDX_CAP: case MAC_PROP_EN_100FDX_CAP: case MAC_PROP_EN_1000FDX_CAP: case MAC_PROP_EN_2500FDX_CAP: mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW); mac_prop_info_set_default_uint8(prh, 1); break; default: break; } } int igc_m_getprop(void *drv, const char *name, mac_prop_id_t prop, uint_t pr_valsize, void *pr_val) { igc_t *igc = drv; int ret = 0; uint8_t *u8p; uint64_t u64; link_flowctrl_t flow; mac_ether_media_t media; mutex_enter(&igc->igc_lock); switch (prop) { case MAC_PROP_DUPLEX: if (pr_valsize < sizeof (link_duplex_t)) { ret = EOVERFLOW; break; } bcopy(&igc->igc_link_duplex, pr_val, sizeof (link_duplex_t)); break; case MAC_PROP_SPEED: if (pr_valsize < sizeof (uint64_t)) { ret = EOVERFLOW; break; } u64 = (uint64_t)igc->igc_link_speed * 1000000; bcopy(&u64, pr_val, sizeof (uint64_t)); break; case MAC_PROP_STATUS: if (pr_valsize < sizeof (link_state_t)) { ret = EOVERFLOW; break; } bcopy(&igc->igc_link_state, pr_val, sizeof (link_state_t)); break; case MAC_PROP_MEDIA: if (pr_valsize < sizeof (mac_ether_media_t)) { ret = EOVERFLOW; break; } media = igc_link_to_media(igc); bcopy(&media, pr_val, sizeof (mac_ether_media_t)); break; case MAC_PROP_AUTONEG: if (pr_valsize < sizeof (uint8_t)) { ret = EOVERFLOW; break; } u8p = pr_val; *u8p = igc->igc_hw.mac.autoneg; break; case MAC_PROP_MTU: if (pr_valsize < sizeof (uint32_t)) { ret = EOVERFLOW; break; } bcopy(&igc->igc_mtu, pr_val, sizeof (uint32_t)); break; case MAC_PROP_FLOWCTRL: switch (igc->igc_hw.fc.requested_mode) { case igc_fc_none: flow = LINK_FLOWCTRL_NONE; break; case igc_fc_rx_pause: flow = LINK_FLOWCTRL_RX; break; case igc_fc_tx_pause: flow = LINK_FLOWCTRL_TX; break; case igc_fc_full: flow = LINK_FLOWCTRL_BI; break; /* * We don't expect to get this value here; however, for * completeness of the switch's valid options we include it and * set it to the common firmware default of enabling * bi-direcitonal pause frames. */ case igc_fc_default: flow = LINK_FLOWCTRL_BI; break; } bcopy(&flow, pr_val, sizeof (link_flowctrl_t)); break; case MAC_PROP_ADV_10HDX_CAP: case MAC_PROP_ADV_10FDX_CAP: case MAC_PROP_ADV_100HDX_CAP: case MAC_PROP_ADV_100FDX_CAP: case MAC_PROP_ADV_1000FDX_CAP: case MAC_PROP_ADV_2500FDX_CAP: ret = ENOTSUP; break; case MAC_PROP_EN_10HDX_CAP: if (pr_valsize < sizeof (uint8_t)) { ret = EOVERFLOW; break; } u8p = pr_val; *u8p = (igc->igc_hw.phy.autoneg_advertised & ADVERTISE_10_HALF) != 0; break; case MAC_PROP_EN_10FDX_CAP: if (pr_valsize < sizeof (uint8_t)) { ret = EOVERFLOW; break; } u8p = pr_val; *u8p = (igc->igc_hw.phy.autoneg_advertised & ADVERTISE_10_FULL) != 0; break; case MAC_PROP_EN_100HDX_CAP: if (pr_valsize < sizeof (uint8_t)) { ret = EOVERFLOW; break; } u8p = pr_val; *u8p = (igc->igc_hw.phy.autoneg_advertised & ADVERTISE_100_HALF) != 0; break; case MAC_PROP_EN_100FDX_CAP: if (pr_valsize < sizeof (uint8_t)) { ret = EOVERFLOW; break; } u8p = pr_val; *u8p = (igc->igc_hw.phy.autoneg_advertised & ADVERTISE_100_FULL) != 0; break; case MAC_PROP_EN_1000FDX_CAP: if (pr_valsize < sizeof (uint8_t)) { ret = EOVERFLOW; break; } u8p = pr_val; *u8p = (igc->igc_hw.phy.autoneg_advertised & ADVERTISE_1000_FULL) != 0; break; case MAC_PROP_EN_2500FDX_CAP: if (pr_valsize < sizeof (uint8_t)) { ret = EOVERFLOW; break; } u8p = pr_val; *u8p = (igc->igc_hw.phy.autoneg_advertised & ADVERTISE_2500_FULL) != 0; break; default: ret = ENOTSUP; break; } mutex_exit(&igc->igc_lock); return (ret); } int igc_m_setprop(void *drv, const char *name, mac_prop_id_t prop, uint_t size, const void *val) { int ret = 0; bool update_link = true; igc_t *igc = drv; uint32_t fc, mtu; uint8_t en; mutex_enter(&igc->igc_lock); switch (prop) { /* * The following properties are always read-only. Note, auto-negotiation * is here because we don't support turning it off right now. We leave * out unsupported speeds. */ case MAC_PROP_DUPLEX: case MAC_PROP_SPEED: case MAC_PROP_STATUS: case MAC_PROP_AUTONEG: case MAC_PROP_ADV_2500FDX_CAP: 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_10HDX_CAP: case MAC_PROP_ADV_10FDX_CAP: case MAC_PROP_MEDIA: ret = ENOTSUP; break; /* * This is a property that we should support, but don't today. */ case MAC_PROP_MTU: /* * Unfortunately, like our siblings igb and e1000g, we do not * currently support changing the MTU dynamically. */ if ((igc->igc_attach & IGC_ATTACH_MAC_START) != 0) { ret = EBUSY; break; } /* * Changing the MTU does not require us to update the link right * now as this can only be done while the device is stopped. */ update_link = false; bcopy(val, &mtu, sizeof (mtu)); if (mtu < ETHERMIN || mtu > igc->igc_limits.il_max_mtu) { ret = EINVAL; break; } /* * Verify that MAC will let us perform this operation. Once we * have confirmed that we will need to update our various buffer * sizes. Right now the driver requires that we increase the rx * buffer size to match the MTU. The tx buffer size is capped at * a page size and will be chained together if required. See the * theory statement for more information. */ ret = mac_maxsdu_update(igc->igc_mac_hdl, mtu); if (ret == 0) { igc->igc_mtu = mtu; igc_hw_buf_update(igc); } break; case MAC_PROP_FLOWCTRL: bcopy(val, &fc, sizeof (uint32_t)); switch (fc) { case LINK_FLOWCTRL_NONE: igc->igc_hw.fc.requested_mode = igc_fc_none; break; case LINK_FLOWCTRL_RX: igc->igc_hw.fc.requested_mode = igc_fc_rx_pause; break; case LINK_FLOWCTRL_TX: igc->igc_hw.fc.requested_mode = igc_fc_tx_pause; break; case LINK_FLOWCTRL_BI: igc->igc_hw.fc.requested_mode = igc_fc_full; break; default: ret = EINVAL; break; } break; case MAC_PROP_EN_2500FDX_CAP: bcopy(val, &en, sizeof (uint8_t)); if (en != 0) { igc->igc_hw.phy.autoneg_advertised |= ADVERTISE_2500_FULL; } else { igc->igc_hw.phy.autoneg_advertised &= ~ADVERTISE_2500_FULL; } break; case MAC_PROP_EN_1000FDX_CAP: bcopy(val, &en, sizeof (uint8_t)); if (en != 0) { igc->igc_hw.phy.autoneg_advertised |= ADVERTISE_1000_FULL; } else { igc->igc_hw.phy.autoneg_advertised &= ~ADVERTISE_1000_FULL; } break; case MAC_PROP_EN_100FDX_CAP: bcopy(val, &en, sizeof (uint8_t)); if (en != 0) { igc->igc_hw.phy.autoneg_advertised |= ADVERTISE_100_FULL; } else { igc->igc_hw.phy.autoneg_advertised &= ~ADVERTISE_100_FULL; } break; case MAC_PROP_EN_100HDX_CAP: bcopy(val, &en, sizeof (uint8_t)); if (en != 0) { igc->igc_hw.phy.autoneg_advertised |= ADVERTISE_100_HALF; } else { igc->igc_hw.phy.autoneg_advertised &= ~ADVERTISE_100_HALF; } break; case MAC_PROP_EN_10FDX_CAP: bcopy(val, &en, sizeof (uint8_t)); if (en != 0) { igc->igc_hw.phy.autoneg_advertised |= ADVERTISE_10_FULL; } else { igc->igc_hw.phy.autoneg_advertised &= ~ADVERTISE_10_FULL; } break; case MAC_PROP_EN_10HDX_CAP: bcopy(val, &en, sizeof (uint8_t)); if (en != 0) { igc->igc_hw.phy.autoneg_advertised |= ADVERTISE_10_HALF; } else { igc->igc_hw.phy.autoneg_advertised &= ~ADVERTISE_10_HALF; } break; default: ret = ENOTSUP; break; } if (ret == 0 && update_link) { if (igc_setup_link(&igc->igc_hw) != IGC_SUCCESS) { ret = EIO; } } mutex_exit(&igc->igc_lock); return (ret); } static mac_callbacks_t igc_mac_callbacks = { .mc_callbacks = MC_GETCAPAB | MC_GETPROP | MC_SETPROP | MC_PROPINFO, .mc_getstat = igc_m_getstat, .mc_start = igc_m_start, .mc_stop = igc_m_stop, .mc_setpromisc = igc_m_setpromisc, .mc_multicst = igc_m_multicast, .mc_getcapab = igc_m_getcapab, .mc_setprop = igc_m_setprop, .mc_getprop = igc_m_getprop, .mc_propinfo = igc_m_propinfo }; bool igc_mac_register(igc_t *igc) { int ret; mac_register_t *mac = mac_alloc(MAC_VERSION); if (mac == NULL) { dev_err(igc->igc_dip, CE_WARN, "failed to allocate mac " "registration handle"); return (false); } mac->m_type_ident = MAC_PLUGIN_IDENT_ETHER; mac->m_driver = igc; mac->m_dip = igc->igc_dip; mac->m_src_addr = igc->igc_hw.mac.addr; mac->m_callbacks = &igc_mac_callbacks; mac->m_min_sdu = 0; mac->m_max_sdu = igc->igc_mtu; mac->m_margin = VLAN_TAGSZ; mac->m_priv_props = NULL; mac->m_v12n = MAC_VIRT_LEVEL1; ret = mac_register(mac, &igc->igc_mac_hdl); mac_free(mac); if (ret != 0) { dev_err(igc->igc_dip, CE_WARN, "failed to register with MAC: " "%d", ret); return (false); } return (true); }