/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright © 2003-2011 Emulex. All rights reserved. */ /* * Copyright (c) 2018, Joyent, Inc. */ /* * Source file containing the implementation of the driver entry points * and related helper functions */ #include #include /* array of properties supported by this driver */ char *oce_priv_props[] = { "_tx_ring_size", "_tx_bcopy_limit", "_rx_ring_size", "_rx_bcopy_limit", NULL }; extern int pow10[]; /* ---[ static function declarations ]----------------------------------- */ static int oce_set_priv_prop(struct oce_dev *dev, const char *name, uint_t size, const void *val); static int oce_get_priv_prop(struct oce_dev *dev, const char *name, uint_t size, void *val); /* ---[ GLD entry points ]----------------------------------------------- */ int oce_m_start(void *arg) { struct oce_dev *dev = arg; int ret; mutex_enter(&dev->dev_lock); if (dev->state & STATE_MAC_STARTED) { mutex_exit(&dev->dev_lock); return (0); } if (dev->suspended) { mutex_exit(&dev->dev_lock); return (EIO); } ret = oce_start(dev); if (ret != DDI_SUCCESS) { mutex_exit(&dev->dev_lock); return (EIO); } dev->state |= STATE_MAC_STARTED; mutex_exit(&dev->dev_lock); return (DDI_SUCCESS); } int oce_start(struct oce_dev *dev) { int qidx = 0; struct link_status link = {0}; /* get link status */ (void) oce_get_link_status(dev, &link); dev->link_status = (link.logical_link_status == NTWK_LOGICAL_LINK_UP) ? LINK_STATE_UP : LINK_STATE_DOWN; dev->link_speed = link.qos_link_speed ? link.qos_link_speed * 10 : pow10[link.mac_speed]; mac_link_update(dev->mac_handle, dev->link_status); for (qidx = 0; qidx < dev->nwqs; qidx++) { (void) oce_start_wq(dev->wq[qidx]); } for (qidx = 0; qidx < dev->nrqs; qidx++) { (void) oce_start_rq(dev->rq[qidx]); } (void) oce_start_mq(dev->mq); /* enable interrupts */ oce_ei(dev); /* arm the eqs */ for (qidx = 0; qidx < dev->neqs; qidx++) { oce_arm_eq(dev, dev->eq[qidx]->eq_id, 0, B_TRUE, B_FALSE); } /* TODO update state */ return (DDI_SUCCESS); } /* oce_start */ void oce_m_stop(void *arg) { struct oce_dev *dev = arg; /* disable interrupts */ mutex_enter(&dev->dev_lock); if (dev->suspended) { mutex_exit(&dev->dev_lock); return; } dev->state |= STATE_MAC_STOPPING; oce_stop(dev); dev->state &= ~(STATE_MAC_STOPPING | STATE_MAC_STARTED); mutex_exit(&dev->dev_lock); } /* called with Tx/Rx comp locks held */ void oce_stop(struct oce_dev *dev) { int qidx; /* disable interrupts */ oce_di(dev); for (qidx = 0; qidx < dev->nwqs; qidx++) { mutex_enter(&dev->wq[qidx]->tx_lock); } mutex_enter(&dev->mq->lock); /* complete the pending Tx */ for (qidx = 0; qidx < dev->nwqs; qidx++) oce_clean_wq(dev->wq[qidx]); /* Release all the locks */ mutex_exit(&dev->mq->lock); for (qidx = 0; qidx < dev->nwqs; qidx++) mutex_exit(&dev->wq[qidx]->tx_lock); if (dev->link_status == LINK_STATE_UP) { dev->link_status = LINK_STATE_UNKNOWN; mac_link_update(dev->mac_handle, dev->link_status); } } /* oce_stop */ int oce_m_multicast(void *arg, boolean_t add, const uint8_t *mca) { struct oce_dev *dev = (struct oce_dev *)arg; struct ether_addr *mca_drv_list; struct ether_addr mca_hw_list[OCE_MAX_MCA]; uint16_t new_mcnt = dev->num_mca; int ret; int i; /* check the address */ if ((mca[0] & 0x1) == 0) { return (EINVAL); } /* Allocate the local array for holding the addresses temporarily */ bzero(&mca_hw_list, sizeof (&mca_hw_list)); mca_drv_list = &dev->multi_cast[0]; DEV_LOCK(dev); if (add) { /* check if we exceeded hw max supported */ if (new_mcnt < OCE_MAX_MCA) { /* copy entire dev mca to the mbx */ bcopy((void*)mca_drv_list, (void*)mca_hw_list, (dev->num_mca * sizeof (struct ether_addr))); /* Append the new one to local list */ bcopy(mca, &mca_hw_list[dev->num_mca], sizeof (struct ether_addr)); } new_mcnt++; } else { struct ether_addr *hwlistp = &mca_hw_list[0]; for (i = 0; i < dev->num_mca; i++) { /* copy only if it does not match */ if (bcmp((mca_drv_list + i), mca, ETHERADDRL)) { bcopy(mca_drv_list + i, hwlistp, ETHERADDRL); hwlistp++; } else { new_mcnt--; } } } if (dev->suspended) { goto finish; } if (new_mcnt > OCE_MAX_MCA) { ret = oce_set_multicast_table(dev, dev->if_id, &mca_hw_list[0], OCE_MAX_MCA, B_TRUE); } else { ret = oce_set_multicast_table(dev, dev->if_id, &mca_hw_list[0], new_mcnt, B_FALSE); } if (ret != 0) { oce_log(dev, CE_WARN, MOD_CONFIG, "mcast %s fails", add ? "ADD" : "DEL"); DEV_UNLOCK(dev); return (EIO); } /* * Copy the local structure to dev structure */ finish: if (new_mcnt && new_mcnt <= OCE_MAX_MCA) { bcopy(mca_hw_list, mca_drv_list, new_mcnt * sizeof (struct ether_addr)); dev->num_mca = (uint16_t)new_mcnt; } DEV_UNLOCK(dev); oce_log(dev, CE_NOTE, MOD_CONFIG, "mcast %s, addr=%02x:%02x:%02x:%02x:%02x:%02x, num_mca=%d", add ? "ADD" : "DEL", mca[0], mca[1], mca[2], mca[3], mca[4], mca[5], dev->num_mca); return (0); } /* oce_m_multicast */ int oce_m_unicast(void *arg, const uint8_t *uca) { struct oce_dev *dev = arg; int ret; DEV_LOCK(dev); if (dev->suspended) { bcopy(uca, dev->unicast_addr, ETHERADDRL); dev->num_smac = 0; DEV_UNLOCK(dev); return (DDI_SUCCESS); } /* Delete previous one and add new one */ ret = oce_del_mac(dev, dev->if_id, &dev->pmac_id); if (ret != DDI_SUCCESS) { DEV_UNLOCK(dev); return (EIO); } dev->num_smac = 0; bzero(dev->unicast_addr, ETHERADDRL); /* Set the New MAC addr earlier is no longer valid */ ret = oce_add_mac(dev, dev->if_id, uca, &dev->pmac_id); if (ret != DDI_SUCCESS) { DEV_UNLOCK(dev); return (EIO); } bcopy(uca, dev->unicast_addr, ETHERADDRL); dev->num_smac = 1; DEV_UNLOCK(dev); return (ret); } /* oce_m_unicast */ /* * Hashing policy for load balancing over the set of TX rings * available to the driver. */ mblk_t * oce_m_send(void *arg, mblk_t *mp) { struct oce_dev *dev = arg; mblk_t *nxt_pkt; mblk_t *rmp = NULL; struct oce_wq *wq; DEV_LOCK(dev); if (dev->suspended || !(dev->state & STATE_MAC_STARTED)) { DEV_UNLOCK(dev); freemsg(mp); return (NULL); } DEV_UNLOCK(dev); /* * Hash to pick a wq */ wq = oce_get_wq(dev, mp); while (mp != NULL) { /* Save the Pointer since mp will be freed in case of copy */ nxt_pkt = mp->b_next; mp->b_next = NULL; /* Hardcode wq since we have only one */ rmp = oce_send_packet(wq, mp); if (rmp != NULL) { /* reschedule Tx */ wq->resched = B_TRUE; oce_arm_cq(dev, wq->cq->cq_id, 0, B_TRUE); /* restore the chain */ rmp->b_next = nxt_pkt; break; } mp = nxt_pkt; } return (rmp); } /* oce_send */ boolean_t oce_m_getcap(void *arg, mac_capab_t cap, void *data) { struct oce_dev *dev = arg; boolean_t ret = B_TRUE; switch (cap) { case MAC_CAPAB_HCKSUM: { uint32_t *csum_flags = u32ptr(data); *csum_flags = HCKSUM_ENABLE | HCKSUM_INET_FULL_V4 | HCKSUM_IPHDRCKSUM; break; } case MAC_CAPAB_LSO: { mac_capab_lso_t *mcap_lso = (mac_capab_lso_t *)data; if (dev->lso_capable) { mcap_lso->lso_flags = LSO_TX_BASIC_TCP_IPV4; mcap_lso->lso_basic_tcp_ipv4.lso_max = OCE_LSO_MAX_SIZE; } else { ret = B_FALSE; } break; } default: ret = B_FALSE; break; } return (ret); } /* oce_m_getcap */ int oce_m_setprop(void *arg, const char *name, mac_prop_id_t id, uint_t size, const void *val) { struct oce_dev *dev = arg; int ret = 0; DEV_LOCK(dev); switch (id) { case MAC_PROP_MTU: { uint32_t mtu; bcopy(val, &mtu, sizeof (uint32_t)); if (dev->mtu == mtu) { ret = 0; break; } if (mtu != OCE_MIN_MTU && mtu != OCE_MAX_MTU) { ret = EINVAL; break; } ret = mac_maxsdu_update(dev->mac_handle, mtu); if (0 == ret) { dev->mtu = mtu; break; } break; } case MAC_PROP_FLOWCTRL: { link_flowctrl_t flowctrl; uint32_t fc = 0; bcopy(val, &flowctrl, sizeof (link_flowctrl_t)); switch (flowctrl) { case LINK_FLOWCTRL_NONE: fc = 0; break; case LINK_FLOWCTRL_RX: fc = OCE_FC_RX; break; case LINK_FLOWCTRL_TX: fc = OCE_FC_TX; break; case LINK_FLOWCTRL_BI: fc = OCE_FC_RX | OCE_FC_TX; break; default: ret = EINVAL; break; } /* switch flowctrl */ if (ret) break; if (fc == dev->flow_control) break; if (dev->suspended) { dev->flow_control = fc; break; } /* call to set flow control */ ret = oce_set_flow_control(dev, fc); /* store the new fc setting on success */ if (ret == 0) { dev->flow_control = fc; } break; } case MAC_PROP_PRIVATE: ret = oce_set_priv_prop(dev, name, size, val); break; default: ret = ENOTSUP; break; } /* switch id */ DEV_UNLOCK(dev); return (ret); } /* oce_m_setprop */ int oce_m_getprop(void *arg, const char *name, mac_prop_id_t id, uint_t size, void *val) { struct oce_dev *dev = arg; uint32_t ret = 0; switch (id) { case MAC_PROP_ADV_10GFDX_CAP: case MAC_PROP_EN_10GFDX_CAP: *(uint8_t *)val = 0x01; break; case MAC_PROP_DUPLEX: { uint32_t *mode = (uint32_t *)val; ASSERT(size >= sizeof (link_duplex_t)); if (dev->state & STATE_MAC_STARTED) *mode = LINK_DUPLEX_FULL; else *mode = LINK_DUPLEX_UNKNOWN; break; } case MAC_PROP_SPEED: { uint64_t *speed = (uint64_t *)val; struct link_status link = {0}; ASSERT(size >= sizeof (uint64_t)); *speed = 0; if (dev->state & STATE_MAC_STARTED) { if (dev->link_speed < 0) { (void) oce_get_link_status(dev, &link); dev->link_speed = link.qos_link_speed ? link.qos_link_speed * 10 : pow10[link.mac_speed]; } *speed = dev->link_speed * 1000000ull; } break; } case MAC_PROP_FLOWCTRL: { link_flowctrl_t *fc = (link_flowctrl_t *)val; ASSERT(size >= sizeof (link_flowctrl_t)); if (dev->flow_control & OCE_FC_TX && dev->flow_control & OCE_FC_RX) *fc = LINK_FLOWCTRL_BI; else if (dev->flow_control == OCE_FC_TX) *fc = LINK_FLOWCTRL_TX; else if (dev->flow_control == OCE_FC_RX) *fc = LINK_FLOWCTRL_RX; else if (dev->flow_control == 0) *fc = LINK_FLOWCTRL_NONE; else ret = EINVAL; break; } case MAC_PROP_PRIVATE: ret = oce_get_priv_prop(dev, name, size, val); break; default: ret = ENOTSUP; break; } /* switch id */ return (ret); } /* oce_m_getprop */ void oce_m_propinfo(void *arg, const char *name, mac_prop_id_t pr_num, mac_prop_info_handle_t prh) { _NOTE(ARGUNUSED(arg)); switch (pr_num) { case MAC_PROP_AUTONEG: case MAC_PROP_EN_AUTONEG: case MAC_PROP_ADV_1000FDX_CAP: case MAC_PROP_EN_1000FDX_CAP: case MAC_PROP_ADV_1000HDX_CAP: case MAC_PROP_EN_1000HDX_CAP: case MAC_PROP_ADV_100FDX_CAP: case MAC_PROP_EN_100FDX_CAP: case MAC_PROP_ADV_100HDX_CAP: case MAC_PROP_EN_100HDX_CAP: case MAC_PROP_ADV_10FDX_CAP: case MAC_PROP_EN_10FDX_CAP: case MAC_PROP_ADV_10HDX_CAP: case MAC_PROP_EN_10HDX_CAP: case MAC_PROP_ADV_100T4_CAP: case MAC_PROP_EN_100T4_CAP: case MAC_PROP_ADV_10GFDX_CAP: case MAC_PROP_EN_10GFDX_CAP: case MAC_PROP_SPEED: case MAC_PROP_DUPLEX: mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); break; case MAC_PROP_MTU: mac_prop_info_set_range_uint32(prh, OCE_MIN_MTU, OCE_MAX_MTU); break; case MAC_PROP_PRIVATE: { char valstr[64]; int value; if (strcmp(name, "_tx_ring_size") == 0) { value = OCE_DEFAULT_TX_RING_SIZE; } else if (strcmp(name, "_rx_ring_size") == 0) { value = OCE_DEFAULT_RX_RING_SIZE; } else { return; } (void) snprintf(valstr, sizeof (valstr), "%d", value); mac_prop_info_set_default_str(prh, valstr); mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ); break; } } } /* oce_m_propinfo */ /* * function to handle dlpi streams message from GLDv3 mac layer */ void oce_m_ioctl(void *arg, queue_t *wq, mblk_t *mp) { struct oce_dev *dev = arg; struct iocblk *iocp; int cmd; uint32_t payload_length; int ret; iocp = (struct iocblk *)voidptr(mp->b_rptr); iocp->ioc_error = 0; cmd = iocp->ioc_cmd; DEV_LOCK(dev); if (dev->suspended) { miocnak(wq, mp, 0, EINVAL); DEV_UNLOCK(dev); return; } DEV_UNLOCK(dev); switch (cmd) { case OCE_ISSUE_MBOX: { ret = oce_issue_mbox(dev, wq, mp, &payload_length); miocack(wq, mp, payload_length, ret); break; } case OCE_QUERY_DRIVER_DATA: { struct oce_driver_query *drv_query = (struct oce_driver_query *)(void *)mp->b_cont->b_rptr; /* if the driver version does not match bail */ if (drv_query->version != OCN_VERSION_SUPPORTED) { oce_log(dev, CE_NOTE, MOD_CONFIG, "%s", "One Connect version mismatch"); miocnak(wq, mp, 0, ENOTSUP); break; } /* fill the return values */ bcopy(OCE_MOD_NAME, drv_query->driver_name, (sizeof (OCE_MOD_NAME) > 32) ? 31 : sizeof (OCE_MOD_NAME)); drv_query->driver_name[31] = '\0'; bcopy(OCE_VERSION, drv_query->driver_version, (sizeof (OCE_VERSION) > 32) ? 31 : sizeof (OCE_VERSION)); drv_query->driver_version[31] = '\0'; if (dev->num_smac == 0) { drv_query->num_smac = 1; bcopy(dev->mac_addr, drv_query->smac_addr[0], ETHERADDRL); } else { drv_query->num_smac = dev->num_smac; bcopy(dev->unicast_addr, drv_query->smac_addr[0], ETHERADDRL); } bcopy(dev->mac_addr, drv_query->pmac_addr, ETHERADDRL); payload_length = sizeof (struct oce_driver_query); miocack(wq, mp, payload_length, 0); break; } default: miocnak(wq, mp, 0, ENOTSUP); break; } } /* oce_m_ioctl */ int oce_m_promiscuous(void *arg, boolean_t enable) { struct oce_dev *dev = arg; int ret = 0; DEV_LOCK(dev); if (dev->promisc == enable) { DEV_UNLOCK(dev); return (ret); } if (dev->suspended) { /* remember the setting */ dev->promisc = enable; DEV_UNLOCK(dev); return (ret); } ret = oce_set_promiscuous(dev, enable); if (ret == DDI_SUCCESS) dev->promisc = enable; DEV_UNLOCK(dev); return (ret); } /* oce_m_promiscuous */ /* * function to set a private property. * Called from the set_prop GLD entry point * * dev - sofware handle to the device * name - string containing the property name * size - length of the string in name * val - pointer to a location where the value to set is stored * * return EINVAL => invalid value in val 0 => success */ static int oce_set_priv_prop(struct oce_dev *dev, const char *name, uint_t size, const void *val) { int ret = ENOTSUP; long result; _NOTE(ARGUNUSED(size)); if (NULL == val) { ret = EINVAL; return (ret); } if (strcmp(name, "_tx_bcopy_limit") == 0) { (void) ddi_strtol(val, (char **)NULL, 0, &result); if (result <= OCE_WQ_BUF_SIZE) { if (result != dev->tx_bcopy_limit) dev->tx_bcopy_limit = (uint32_t)result; ret = 0; } else { ret = EINVAL; } } if (strcmp(name, "_rx_bcopy_limit") == 0) { (void) ddi_strtol(val, (char **)NULL, 0, &result); if (result <= OCE_RQ_BUF_SIZE) { if (result != dev->rx_bcopy_limit) dev->rx_bcopy_limit = (uint32_t)result; ret = 0; } else { ret = EINVAL; } } return (ret); } /* oce_set_priv_prop */ /* * function to get the value of a private property. Called from get_prop * * dev - software handle to the device * name - string containing the property name * size - length of the string contained name * val - [OUT] pointer to the location where the result is returned * * return EINVAL => invalid request 0 => success */ static int oce_get_priv_prop(struct oce_dev *dev, const char *name, uint_t size, void *val) { int value; if (strcmp(name, "_tx_ring_size") == 0) { value = dev->tx_ring_size; } else if (strcmp(name, "_tx_bcopy_limit") == 0) { value = dev->tx_bcopy_limit; } else if (strcmp(name, "_rx_ring_size") == 0) { value = dev->rx_ring_size; } else if (strcmp(name, "_rx_bcopy_limit") == 0) { value = dev->rx_bcopy_limit; } else { return (ENOTSUP); } (void) snprintf(val, size, "%d", value); return (0); } /* oce_get_priv_prop */