/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Data-Link Services Module */ #include #include #include #include #include #include #include #include #include #include #include #include #include static kmem_cache_t *i_dls_impl_cachep; static uint32_t i_dls_impl_count; /* * Private functions. */ /*ARGSUSED*/ static int i_dls_constructor(void *buf, void *arg, int kmflag) { dls_impl_t *dip = buf; bzero(buf, sizeof (dls_impl_t)); rw_init(&(dip->di_lock), NULL, RW_DRIVER, NULL); return (0); } /*ARGSUSED*/ static void i_dls_destructor(void *buf, void *arg) { dls_impl_t *dip = buf; ASSERT(dip->di_dvp == NULL); ASSERT(dip->di_mnh == NULL); ASSERT(dip->di_dmap == NULL); ASSERT(!dip->di_bound); ASSERT(dip->di_rx == NULL); ASSERT(dip->di_tx == NULL); rw_destroy(&(dip->di_lock)); } static void i_dls_notify(void *arg, mac_notify_type_t type) { dls_impl_t *dip = arg; switch (type) { case MAC_NOTE_UNICST: mac_unicst_get(dip->di_mh, dip->di_unicst_addr); break; case MAC_NOTE_PROMISC: /* * Every time the MAC interface changes promiscuity we * need to reset the transmit function: * * - In non-promiscuous mode we simply copy the function * pointer from the MAC module. * * - In promiscuous mode we use mac_txloop(), which will * handle the loopback case. */ if (mac_promisc_get(dip->di_mh, MAC_PROMISC)) { dip->di_tx = mac_txloop; dip->di_tx_arg = dip->di_mh; } else { mac_tx_get(dip->di_mh, &dip->di_tx, &dip->di_tx_arg); } break; } } static mblk_t * i_dls_ether_header(dls_impl_t *dip, const uint8_t *daddr, uint16_t sap, uint_t pri) { struct ether_header *ehp; struct ether_vlan_header *evhp; const mac_info_t *mip; uint_t addr_length; uint16_t vid; mblk_t *mp; mip = dip->di_mip; addr_length = mip->mi_addr_length; /* * Check whether the DLSAP value is legal for ethernet. */ if (!SAP_LEGAL(mip->mi_media, sap)) return (NULL); /* * If the interface is a VLAN interface then we need VLAN packet * headers. */ if ((vid = dip->di_dvp->dv_id) != VLAN_ID_NONE) goto vlan; /* * Allocate a normal ethernet packet header. */ if ((mp = allocb(sizeof (struct ether_header), BPRI_HI)) == NULL) return (NULL); /* * Copy in the given address as the destination, our current unicast * address as the source and the given sap as the type/length. */ ehp = (struct ether_header *)mp->b_rptr; bcopy(daddr, &(ehp->ether_dhost), addr_length); bcopy(dip->di_unicst_addr, &(ehp->ether_shost), addr_length); ehp->ether_type = htons(sap); mp->b_wptr += sizeof (struct ether_header); return (mp); vlan: /* * Allocate a VLAN ethernet packet header. */ if ((mp = allocb(sizeof (struct ether_vlan_header), BPRI_HI)) == NULL) return (NULL); /* * Copy in the given address as the destination, our current unicast * address as the source, the VLAN tpid and tci and the given sap as * the type/length. */ evhp = (struct ether_vlan_header *)mp->b_rptr; bcopy(daddr, &(evhp->ether_dhost), addr_length); bcopy(dip->di_unicst_addr, &(evhp->ether_shost), addr_length); evhp->ether_tpid = htons(VLAN_TPID); evhp->ether_tci = htons(VLAN_TCI(pri, ETHER_CFI, vid)); evhp->ether_type = htons(sap); mp->b_wptr += sizeof (struct ether_vlan_header); return (mp); } /*ARGSUSED*/ static void i_dls_ether_header_info(dls_impl_t *dip, mblk_t *mp, dls_header_info_t *dhip) { struct ether_header *ehp; struct ether_vlan_header *evhp; uint16_t type_length; uint16_t tci; ASSERT(MBLKL(mp) >= sizeof (struct ether_header)); ehp = (struct ether_header *)mp->b_rptr; /* * Determine whether to parse a normal or VLAN ethernet header. */ if ((type_length = ntohs(ehp->ether_type)) == VLAN_TPID) goto vlan; /* * Specify the length of the header. */ dhip->dhi_length = sizeof (struct ether_header); /* * Get the destination address. */ dhip->dhi_daddr = (const uint8_t *)&(ehp->ether_dhost); /* * If the destination address was a group address then * dl_group_address field should be non-zero. */ dhip->dhi_isgroup = (dhip->dhi_daddr[0] & 0x01); /* * Get the source address. */ dhip->dhi_saddr = (uint8_t *)&(ehp->ether_shost); /* * Get the ethertype */ dhip->dhi_ethertype = (type_length > ETHERMTU) ? type_length : 0; /* * The VLAN identifier must be VLAN_ID_NONE. */ dhip->dhi_vid = VLAN_ID_NONE; return; vlan: ASSERT(MBLKL(mp) >= sizeof (struct ether_vlan_header)); evhp = (struct ether_vlan_header *)mp->b_rptr; /* * Specify the length of the header. */ dhip->dhi_length = sizeof (struct ether_vlan_header); /* * Get the destination address. */ dhip->dhi_daddr = (const uint8_t *)&(evhp->ether_dhost); /* * If the destination address was a group address then * dl_group_address field should be non-zero. */ dhip->dhi_isgroup = (dhip->dhi_daddr[0] & 0x01); /* * Get the source address. */ dhip->dhi_saddr = (uint8_t *)&(evhp->ether_shost); /* * Get the ethertype */ type_length = ntohs(evhp->ether_type); dhip->dhi_ethertype = (type_length > ETHERMTU) ? type_length : 0; ASSERT(dhip->dhi_ethertype != VLAN_TPID); /* * Get the VLAN identifier. */ tci = ntohs(evhp->ether_tci); dhip->dhi_vid = VLAN_ID(tci); } /* * Module initialization functions. */ void dls_init(void) { /* * Create a kmem_cache of dls_impl_t. */ i_dls_impl_cachep = kmem_cache_create("dls_cache", sizeof (dls_impl_t), 0, i_dls_constructor, i_dls_destructor, NULL, NULL, NULL, 0); ASSERT(i_dls_impl_cachep != NULL); } int dls_fini(void) { /* * If there are any dls_impl_t in use then return EBUSY. */ if (i_dls_impl_count != 0) return (EBUSY); /* * Destroy the kmem_cache. */ kmem_cache_destroy(i_dls_impl_cachep); return (0); } /* * Client function. */ int dls_create(const char *name, const char *dev, uint_t port, uint16_t vid) { return (dls_vlan_create(name, dev, port, vid)); } int dls_destroy(const char *name) { return (dls_vlan_destroy(name)); } int dls_open(const char *name, dls_channel_t *dcp) { dls_impl_t *dip; dls_vlan_t *dvp; dls_link_t *dlp; int err; /* * Get a reference to the named dls_vlan_t. */ if ((err = dls_vlan_hold(name, &dvp)) != 0) return (err); /* * Allocate a new dls_impl_t. */ dip = kmem_cache_alloc(i_dls_impl_cachep, KM_SLEEP); dip->di_dvp = dvp; /* * Cache a copy of the MAC interface handle, a pointer to the * immutable MAC info and a copy of the current MAC address. */ dlp = dvp->dv_dlp; dip->di_mh = dlp->dl_mh; dip->di_mip = dlp->dl_mip; mac_unicst_get(dip->di_mh, dip->di_unicst_addr); /* * Set the MAC transmit function. * * NOTE: We know that the MAC is not in promiscuous mode so we * simply copy the values from the MAC. * (See comment in i_dls_notify() for further explanation). */ mac_tx_get(dip->di_mh, &dip->di_tx, &dip->di_tx_arg); /* * Set up packet header constructor and parser functions. (We currently * only support ethernet). */ ASSERT(dip->di_mip->mi_media == DL_ETHER); dip->di_header = i_dls_ether_header; dip->di_header_info = i_dls_ether_header_info; /* * Add a notification function so that we get updates from the MAC. */ dip->di_mnh = mac_notify_add(dip->di_mh, i_dls_notify, (void *)dip); /* * Bump the kmem_cache count to make sure it is not prematurely * destroyed. */ atomic_add_32(&i_dls_impl_count, 1); /* * Hand back a reference to the dls_impl_t. */ *dcp = (dls_channel_t)dip; return (0); } void dls_close(dls_channel_t dc) { dls_impl_t *dip = (dls_impl_t *)dc; dls_vlan_t *dvp; dls_link_t *dlp; dls_multicst_addr_t *p; dls_multicst_addr_t *nextp; dls_active_clear(dc); rw_enter(&(dip->di_lock), RW_WRITER); /* * Remove the notify function. */ mac_notify_remove(dip->di_mh, dip->di_mnh); dip->di_mnh = NULL; /* * If the dls_impl_t is bound then unbind it. */ dvp = dip->di_dvp; dlp = dvp->dv_dlp; if (dip->di_bound) { rw_exit(&(dip->di_lock)); dls_link_remove(dlp, dip); rw_enter(&(dip->di_lock), RW_WRITER); dip->di_rx = NULL; dip->di_rx_arg = NULL; dip->di_bound = B_FALSE; } /* * Walk the list of multicast addresses, disabling each at the MAC. */ for (p = dip->di_dmap; p != NULL; p = nextp) { (void) mac_multicst_remove(dip->di_mh, p->dma_addr); nextp = p->dma_nextp; kmem_free(p, sizeof (dls_multicst_addr_t)); } dip->di_dmap = NULL; rw_exit(&(dip->di_lock)); /* * If the MAC has been set in promiscuous mode then disable it. */ (void) dls_promisc(dc, 0); /* * Free the dls_impl_t back to the cache. */ dip->di_dvp = NULL; dip->di_tx = NULL; dip->di_tx_arg = NULL; kmem_cache_free(i_dls_impl_cachep, dip); /* * Decrement the reference count to allow the cache to be destroyed * if there are no more dls_impl_t. */ atomic_add_32(&i_dls_impl_count, -1); /* * Release our reference to the dls_vlan_t allowing that to be * destroyed if there are no more dls_impl_t. */ dls_vlan_rele(dvp); } mac_handle_t dls_mac(dls_channel_t dc) { dls_impl_t *dip = (dls_impl_t *)dc; return (dip->di_mh); } uint16_t dls_vid(dls_channel_t dc) { dls_impl_t *dip = (dls_impl_t *)dc; return (dip->di_dvp->dv_id); } int dls_bind(dls_channel_t dc, uint16_t sap) { dls_impl_t *dip = (dls_impl_t *)dc; dls_link_t *dlp; /* * Check to see the value is legal for the media type. */ if (!SAP_LEGAL(dip->di_mip->mi_media, sap)) return (EINVAL); /* * Set up the dls_impl_t to mark it as able to receive packets. */ rw_enter(&(dip->di_lock), RW_WRITER); ASSERT(!dip->di_bound); dip->di_sap = sap; dip->di_bound = B_TRUE; rw_exit(&(dip->di_lock)); /* * Now bind the dls_impl_t by adding it into the hash table in the * dls_link_t. * * NOTE: This must be done without the dls_impl_t lock being held * otherwise deadlock may ensue. */ dlp = dip->di_dvp->dv_dlp; dls_link_add(dlp, (dip->di_promisc & DLS_PROMISC_SAP) ? DLS_SAP_PROMISC : (uint32_t)sap, dip); return (0); } void dls_unbind(dls_channel_t dc) { dls_impl_t *dip = (dls_impl_t *)dc; dls_link_t *dlp; /* * Unbind the dls_impl_t by removing it from the hash table in the * dls_link_t. * * NOTE: This must be done without the dls_impl_t lock being held * otherise deadlock may enuse. */ dlp = dip->di_dvp->dv_dlp; dls_link_remove(dlp, dip); /* * Mark the dls_impl_t as unable to receive packets This will make * sure that 'receives in flight' will not come our way. */ dip->di_bound = B_FALSE; } int dls_promisc(dls_channel_t dc, uint32_t flags) { dls_impl_t *dip = (dls_impl_t *)dc; dls_link_t *dlp; int err = 0; ASSERT(!(flags & ~(DLS_PROMISC_SAP | DLS_PROMISC_MULTI | DLS_PROMISC_PHYS))); /* * Check if we need to turn on 'all sap' mode. */ rw_enter(&(dip->di_lock), RW_WRITER); dlp = dip->di_dvp->dv_dlp; if ((flags & DLS_PROMISC_SAP) && !(dip->di_promisc & DLS_PROMISC_SAP)) { dip->di_promisc |= DLS_PROMISC_SAP; if (!dip->di_bound) goto multi; rw_exit(&(dip->di_lock)); dls_link_remove(dlp, dip); dls_link_add(dlp, DLS_SAP_PROMISC, dip); rw_enter(&(dip->di_lock), RW_WRITER); goto multi; } /* * Check if we need to turn off 'all sap' mode. */ if (!(flags & DLS_PROMISC_SAP) && (dip->di_promisc & DLS_PROMISC_SAP)) { dip->di_promisc &= ~DLS_PROMISC_SAP; if (!dip->di_bound) goto multi; rw_exit(&(dip->di_lock)); dls_link_remove(dlp, dip); dls_link_add(dlp, dip->di_sap, dip); rw_enter(&(dip->di_lock), RW_WRITER); } multi: /* * It's easiest to add the txloop handler up-front; if promiscuous * mode cannot be enabled, then we'll remove it before returning. */ if (dlp->dl_npromisc == 0 && (flags & (DLS_PROMISC_MULTI|DLS_PROMISC_PHYS))) { ASSERT(dlp->dl_mth == NULL); dlp->dl_mth = mac_txloop_add(dlp->dl_mh, dlp->dl_loopback, dlp); } /* * Check if we need to turn on 'all multicast' mode. */ if ((flags & DLS_PROMISC_MULTI) && !(dip->di_promisc & DLS_PROMISC_MULTI)) { err = mac_promisc_set(dip->di_mh, B_TRUE, MAC_PROMISC); if (err != 0) goto done; dip->di_promisc |= DLS_PROMISC_MULTI; dlp->dl_npromisc++; goto phys; } /* * Check if we need to turn off 'all multicast' mode. */ if (!(flags & DLS_PROMISC_MULTI) && (dip->di_promisc & DLS_PROMISC_MULTI)) { err = mac_promisc_set(dip->di_mh, B_FALSE, MAC_PROMISC); if (err != 0) goto done; dip->di_promisc &= ~DLS_PROMISC_MULTI; dlp->dl_npromisc--; } phys: /* * Check if we need to turn on 'all physical' mode. */ if ((flags & DLS_PROMISC_PHYS) && !(dip->di_promisc & DLS_PROMISC_PHYS)) { err = mac_promisc_set(dip->di_mh, B_TRUE, MAC_PROMISC); if (err != 0) goto done; dip->di_promisc |= DLS_PROMISC_PHYS; dlp->dl_npromisc++; goto done; } /* * Check if we need to turn off 'all physical' mode. */ if (!(flags & DLS_PROMISC_PHYS) && (dip->di_promisc & DLS_PROMISC_PHYS)) { err = mac_promisc_set(dip->di_mh, B_FALSE, MAC_PROMISC); if (err != 0) goto done; dip->di_promisc &= ~DLS_PROMISC_PHYS; dlp->dl_npromisc--; } done: if (dlp->dl_npromisc == 0 && dlp->dl_mth != NULL) { mac_txloop_remove(dlp->dl_mh, dlp->dl_mth); dlp->dl_mth = NULL; } ASSERT(dlp->dl_npromisc == 0 || dlp->dl_mth != NULL); rw_exit(&(dip->di_lock)); return (err); } int dls_multicst_add(dls_channel_t dc, const uint8_t *addr) { dls_impl_t *dip = (dls_impl_t *)dc; int err; dls_multicst_addr_t **pp; dls_multicst_addr_t *p; uint_t addr_length; /* * Check whether the address is in the list of enabled addresses for * this dls_impl_t. */ rw_enter(&(dip->di_lock), RW_WRITER); addr_length = dip->di_mip->mi_addr_length; for (pp = &(dip->di_dmap); (p = *pp) != NULL; pp = &(p->dma_nextp)) { if (bcmp(addr, p->dma_addr, addr_length) == 0) { /* * It is there so there's nothing to do. */ err = 0; goto done; } } /* * Allocate a new list item. */ if ((p = kmem_zalloc(sizeof (dls_multicst_addr_t), KM_NOSLEEP)) == NULL) { err = ENOMEM; goto done; } /* * Enable the address at the MAC. */ if ((err = mac_multicst_add(dip->di_mh, addr)) != 0) { kmem_free(p, sizeof (dls_multicst_addr_t)); goto done; } /* * The address is now enabled at the MAC so add it to the list. */ bcopy(addr, p->dma_addr, addr_length); *pp = p; done: rw_exit(&(dip->di_lock)); return (err); } int dls_multicst_remove(dls_channel_t dc, const uint8_t *addr) { dls_impl_t *dip = (dls_impl_t *)dc; int err; dls_multicst_addr_t **pp; dls_multicst_addr_t *p; uint_t addr_length; /* * Find the address in the list of enabled addresses for this * dls_impl_t. */ rw_enter(&(dip->di_lock), RW_WRITER); addr_length = dip->di_mip->mi_addr_length; for (pp = &(dip->di_dmap); (p = *pp) != NULL; pp = &(p->dma_nextp)) { if (bcmp(addr, p->dma_addr, addr_length) == 0) break; } /* * If we walked to the end of the list then the given address is * not currently enabled for this dls_impl_t. */ if (p == NULL) { err = ENOENT; goto done; } /* * Disable the address at the MAC. */ if ((err = mac_multicst_remove(dip->di_mh, addr)) != 0) goto done; /* * Remove the address from the list. */ *pp = p->dma_nextp; kmem_free(p, sizeof (dls_multicst_addr_t)); done: rw_exit(&(dip->di_lock)); return (err); } mblk_t * dls_header(dls_channel_t dc, const uint8_t *addr, uint16_t sap, uint_t pri) { dls_impl_t *dip = (dls_impl_t *)dc; return (dip->di_header(dip, addr, sap, pri)); } void dls_header_info(dls_channel_t dc, mblk_t *mp, dls_header_info_t *dhip) { dls_impl_t *dip = (dls_impl_t *)dc; dip->di_header_info(dip, mp, dhip); } void dls_rx_set(dls_channel_t dc, dls_rx_t rx, void *arg) { dls_impl_t *dip = (dls_impl_t *)dc; rw_enter(&(dip->di_lock), RW_WRITER); dip->di_rx = rx; dip->di_rx_arg = arg; rw_exit(&(dip->di_lock)); } mblk_t * dls_tx(dls_channel_t dc, mblk_t *mp) { dls_impl_t *dip = (dls_impl_t *)dc; return (dip->di_tx(dip->di_tx_arg, mp)); } /* * Exported functions. */ #define ADDR_MATCH(_addr_a, _addr_b, _length, _match) \ { \ uint_t i; \ \ /* \ * Make sure the addresses are 16 bit aligned and that \ * the length is an even number of octets. \ */ \ ASSERT(IS_P2ALIGNED((_addr_a), sizeof (uint16_t))); \ ASSERT(IS_P2ALIGNED((_addr_b), sizeof (uint16_t))); \ ASSERT((_length & 1) == 0); \ \ (_match) = B_TRUE; \ for (i = 0; i < (_length) >> 1; i++) { \ if (((uint16_t *)(_addr_a))[i] != \ ((uint16_t *)(_addr_b))[i]) { \ (_match) = B_FALSE; \ break; \ } \ } \ } boolean_t dls_accept(dls_impl_t *dip, const uint8_t *daddr) { boolean_t match; dls_multicst_addr_t *dmap; uint_t addr_length = dip->di_mip->mi_addr_length; /* * We must not accept packets if the dls_impl_t is not marked as bound * or is being removed. */ rw_enter(&(dip->di_lock), RW_READER); if (!dip->di_bound || dip->di_removing) goto refuse; /* * If the dls_impl_t is in 'all physical' mode then always accept. */ if (dip->di_promisc & DLS_PROMISC_PHYS) goto accept; /* * Check to see if the destination address matches the dls_impl_t * unicast address. */ ADDR_MATCH(daddr, dip->di_unicst_addr, addr_length, match); if (match) goto accept; /* * Check for a 'group' address. If it is not then refuse it since we * already know it does not match the unicast address. */ if (!(daddr[0] & 0x01)) goto refuse; /* * If the address is broadcast then the dls_impl_t will always accept * it. */ ADDR_MATCH(daddr, dip->di_mip->mi_brdcst_addr, addr_length, match); if (match) goto accept; /* * If a group address is not broadcast then it must be multicast so * check it against the list of addresses enabled for this dls_impl_t * or accept it unconditionally if the dls_impl_t is in 'all * multicast' mode. */ if (dip->di_promisc & DLS_PROMISC_MULTI) goto accept; for (dmap = dip->di_dmap; dmap != NULL; dmap = dmap->dma_nextp) { ADDR_MATCH(daddr, dmap->dma_addr, addr_length, match); if (match) goto accept; } refuse: rw_exit(&(dip->di_lock)); return (B_FALSE); accept: rw_exit(&(dip->di_lock)); return (B_TRUE); } /*ARGSUSED*/ boolean_t dls_accept_loopback(dls_impl_t *dip, const uint8_t *daddr) { /* * We must not accept packets if the dls_impl_t is not marked as bound * or is being removed. */ rw_enter(&(dip->di_lock), RW_READER); if (!dip->di_bound || dip->di_removing) goto refuse; /* * A dls_impl_t should only accept loopback packets if it is in * 'all physical' mode. */ if (dip->di_promisc & DLS_PROMISC_PHYS) goto accept; refuse: rw_exit(&(dip->di_lock)); return (B_FALSE); accept: rw_exit(&(dip->di_lock)); return (B_TRUE); } boolean_t dls_active_set(dls_channel_t dc) { dls_impl_t *dip = (dls_impl_t *)dc; dls_link_t *dlp = dip->di_dvp->dv_dlp; rw_enter(&dip->di_lock, RW_WRITER); /* If we're already active, then there's nothing more to do. */ if (dip->di_active) { rw_exit(&dip->di_lock); return (B_TRUE); } /* * If this is the first active client on this link, notify * the mac that we're becoming an active client. */ if (dlp->dl_nactive == 0 && !mac_active_set(dlp->dl_mh)) { rw_exit(&dip->di_lock); return (B_FALSE); } dip->di_active = B_TRUE; mutex_enter(&dlp->dl_lock); dlp->dl_nactive++; mutex_exit(&dlp->dl_lock); rw_exit(&dip->di_lock); return (B_TRUE); } void dls_active_clear(dls_channel_t dc) { dls_impl_t *dip = (dls_impl_t *)dc; dls_link_t *dlp = dip->di_dvp->dv_dlp; rw_enter(&dip->di_lock, RW_WRITER); if (!dip->di_active) goto out; dip->di_active = B_FALSE; mutex_enter(&dlp->dl_lock); if (--dlp->dl_nactive == 0) mac_active_clear(dip->di_mh); mutex_exit(&dlp->dl_lock); out: rw_exit(&dip->di_lock); }