| /* |
| * Copyright (c) 1997 |
| * Jonathan Stone and Jason R. Thorpe. All rights reserved. |
| * |
| * This software is derived from information provided by Matt Thomas. |
| * |
| * 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. All advertising materials mentioning features or use of this software |
| * must display the following acknowledgement: |
| * This product includes software developed by Jonathan Stone |
| * and Jason R. Thorpe for the NetBSD Project. |
| * 4. The names of the authors may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHOR 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. |
| * |
| * $Id: if_media.c 1721 2006-09-20 08:45:13Z mentor $ |
| */ |
| |
| /* |
| * BSD/OS-compatible network interface media selection. |
| * |
| * Where it is safe to do so, this code strays slightly from the BSD/OS |
| * design. Software which uses the API (device drivers, basically) |
| * shouldn't notice any difference. |
| * |
| * Many thanks to Matt Thomas for providing the information necessary |
| * to implement this interface. |
| */ |
| |
| #ifndef EXPORT_SYMTAB |
| #define EXPORT_SYMTAB |
| #endif |
| |
| #ifndef AUTOCONF_INCLUDED |
| #include <linux/config.h> |
| #endif |
| #include <linux/version.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/if.h> |
| #include <linux/netdevice.h> |
| #ifndef ifr_media |
| #define ifr_media ifr_ifru.ifru_ivalue |
| #endif |
| |
| #include <asm/uaccess.h> |
| |
| #include "net80211/if_media.h" |
| |
| /* |
| * Compile-time options: |
| * IFMEDIA_DEBUG: |
| * turn on implementation-level debug printfs. |
| * Useful for debugging newly-ported drivers. |
| */ |
| |
| struct ifmedia_entry *ifmedia_match(struct ifmedia *, int, int); |
| |
| #ifdef IFMEDIA_DEBUG |
| int ifmedia_debug = 0; |
| static void ifmedia_printword(int); |
| #endif |
| |
| /* |
| * Initialize if_media struct for a specific interface instance. |
| */ |
| void |
| ifmedia_init(struct ifmedia *ifm, int dontcare_mask, |
| ifm_change_cb_t change_callback, ifm_stat_cb_t status_callback) |
| { |
| LIST_INIT(&ifm->ifm_list); |
| ifm->ifm_cur = NULL; |
| ifm->ifm_media = 0; |
| ifm->ifm_mask = dontcare_mask; /* IF don't-care bits */ |
| ifm->ifm_change = change_callback; |
| ifm->ifm_status = status_callback; |
| } |
| |
| void |
| ifmedia_removeall(struct ifmedia *ifm) |
| { |
| struct ifmedia_entry *entry; |
| |
| for (entry = LIST_FIRST(&ifm->ifm_list); entry; |
| entry = LIST_FIRST(&ifm->ifm_list)) { |
| LIST_REMOVE(entry, ifm_list); |
| kfree(entry); |
| } |
| } |
| |
| /* |
| * Add a media configuration to the list of supported media |
| * for a specific interface instance. |
| */ |
| void |
| ifmedia_add(struct ifmedia *ifm, int mword, int data, void *aux) |
| { |
| register struct ifmedia_entry *entry; |
| |
| #ifdef IFMEDIA_DEBUG |
| if (ifmedia_debug) { |
| if (ifm == NULL) { |
| printk("ifmedia_add: null ifm\n"); |
| return; |
| } |
| printk("Adding entry for "); |
| ifmedia_printword(mword); |
| } |
| #endif |
| |
| entry = kmalloc(sizeof(*entry), GFP_KERNEL); |
| if (entry == NULL) |
| panic("ifmedia_add: can't malloc entry"); |
| |
| entry->ifm_media = mword; |
| entry->ifm_data = data; |
| entry->ifm_aux = aux; |
| |
| LIST_INSERT_HEAD(&ifm->ifm_list, entry, ifm_list); |
| } |
| |
| /* |
| * Add an array of media configurations to the list of |
| * supported media for a specific interface instance. |
| */ |
| void |
| ifmedia_list_add(struct ifmedia *ifm, struct ifmedia_entry *lp, int count) |
| { |
| int i; |
| |
| for (i = 0; i < count; i++) |
| ifmedia_add(ifm, lp[i].ifm_media, lp[i].ifm_data, lp[i].ifm_aux); |
| } |
| |
| /* |
| * Set the default active media. |
| * |
| * Called by device-specific code which is assumed to have already |
| * selected the default media in hardware. We do _not_ call the |
| * media-change callback. |
| */ |
| void |
| ifmedia_set(struct ifmedia *ifm, int target) |
| { |
| struct ifmedia_entry *match; |
| |
| match = ifmedia_match(ifm, target, ifm->ifm_mask); |
| |
| if (match == NULL) { |
| printk("ifmedia_set: no match for 0x%x/0x%x\n", |
| target, ~ifm->ifm_mask); |
| panic("ifmedia_set"); |
| } |
| ifm->ifm_cur = match; |
| |
| #ifdef IFMEDIA_DEBUG |
| if (ifmedia_debug) { |
| printk("ifmedia_set: target "); |
| ifmedia_printword(target); |
| printk("ifmedia_set: setting to "); |
| ifmedia_printword(ifm->ifm_cur->ifm_media); |
| } |
| #endif |
| } |
| |
| /* |
| * Device-independent media ioctl support function. |
| */ |
| int |
| ifmedia_ioctl(struct net_device *dev, struct ifreq *ifr, |
| struct ifmedia *ifm, u_long cmd) |
| { |
| struct ifmedia_entry *match; |
| struct ifmediareq *ifmr = (struct ifmediareq *) ifr; |
| int error = 0, sticky; |
| |
| if (dev == NULL || ifr == NULL || ifm == NULL) |
| return -EINVAL; |
| |
| switch (cmd) { |
| /* |
| * Set the current media. |
| */ |
| case SIOCSIFMEDIA: |
| { |
| struct ifmedia_entry *oldentry; |
| int oldmedia; |
| int newmedia = ifr->ifr_media; |
| |
| match = ifmedia_match(ifm, newmedia, ifm->ifm_mask); |
| if (match == NULL) { |
| #ifdef IFMEDIA_DEBUG |
| if (ifmedia_debug) { |
| printk("ifmedia_ioctl: no media found for 0x%x\n", |
| newmedia); |
| } |
| #endif |
| return -ENXIO; |
| } |
| |
| /* |
| * If no change, we're done. |
| * XXX Automedia may invole software intervention. |
| * Keep going in case the the connected media changed. |
| * Similarly, if best match changed (kernel debugger?). |
| */ |
| if ((IFM_SUBTYPE(newmedia) != IFM_AUTO) && |
| (newmedia == ifm->ifm_media) && |
| (match == ifm->ifm_cur)) |
| return 0; |
| |
| /* |
| * We found a match, now make the driver switch to it. |
| * Make sure to preserve our old media type in case the |
| * driver can't switch. |
| */ |
| #ifdef IFMEDIA_DEBUG |
| if (ifmedia_debug) { |
| printk("ifmedia_ioctl: switching %s to ", dev->name); |
| ifmedia_printword(match->ifm_media); |
| } |
| #endif |
| oldentry = ifm->ifm_cur; |
| oldmedia = ifm->ifm_media; |
| ifm->ifm_cur = match; |
| ifm->ifm_media = newmedia; |
| error = (*ifm->ifm_change)(netdev_priv(dev)); |
| if ((error < 0) && (error != -ENETRESET)) { |
| ifm->ifm_cur = oldentry; |
| ifm->ifm_media = oldmedia; |
| } |
| break; |
| } |
| |
| /* |
| * Get list of available media and current media on interface. |
| */ |
| case SIOCGIFMEDIA: |
| { |
| struct ifmedia_entry *ep; |
| int *kptr, count; |
| int usermax; /* user requested max */ |
| |
| kptr = NULL; /* XXX gcc */ |
| |
| ifmr->ifm_active = ifmr->ifm_current = ifm->ifm_cur ? |
| ifm->ifm_cur->ifm_media : IFM_NONE; |
| ifmr->ifm_mask = ifm->ifm_mask; |
| ifmr->ifm_status = 0; |
| (*ifm->ifm_status)(netdev_priv(dev), ifmr); |
| |
| count = 0; |
| usermax = 0; |
| |
| /* |
| * If there are more interfaces on the list, count |
| * them. This allows the caller to set ifmr->ifm_count |
| * to 0 on the first call to know how much space to |
| * allocate. |
| */ |
| LIST_FOREACH(ep, &ifm->ifm_list, ifm_list) |
| usermax++; |
| |
| /* |
| * Don't allow the user to ask for too many |
| * or a negative number. |
| */ |
| if (ifmr->ifm_count > usermax) |
| ifmr->ifm_count = usermax; |
| else if (ifmr->ifm_count < 0) |
| return (-EINVAL); |
| |
| if (ifmr->ifm_count != 0) { |
| kptr = (int *)kmalloc(ifmr->ifm_count * sizeof(int), |
| GFP_KERNEL); |
| |
| if (kptr == NULL) |
| return (-ENOMEM); |
| /* |
| * Get the media words from the interface's list. |
| */ |
| ep = LIST_FIRST(&ifm->ifm_list); |
| for (; ep != NULL && count < ifmr->ifm_count; |
| ep = LIST_NEXT(ep, ifm_list), count++) |
| kptr[count] = ep->ifm_media; |
| |
| if (ep != NULL) |
| error = -E2BIG; /* oops! */ |
| } else |
| count = usermax; |
| |
| /* |
| * We do the copyout on E2BIG, because that's |
| * just our way of telling userland that there |
| * are more. This is the behavior I've observed |
| * under BSD/OS 3.0 |
| */ |
| sticky = error; |
| if ((error == 0 || error == -E2BIG) && ifmr->ifm_count != 0) { |
| error = copy_to_user(ifmr->ifm_ulist, |
| kptr, ifmr->ifm_count * sizeof(int)); |
| } |
| |
| if (error == 0) |
| error = sticky; |
| |
| if (ifmr->ifm_count != 0) |
| kfree(kptr); |
| |
| ifmr->ifm_count = count; |
| break; |
| } |
| |
| default: |
| return -EINVAL; |
| } |
| |
| return error; |
| } |
| |
| /* |
| * Find media entry matching a given ifm word. |
| * |
| */ |
| struct ifmedia_entry * |
| ifmedia_match(struct ifmedia *ifm, int target, int mask) |
| { |
| struct ifmedia_entry *match, *next; |
| |
| match = NULL; |
| mask = ~mask; |
| |
| LIST_FOREACH(next, &ifm->ifm_list, ifm_list) { |
| if ((next->ifm_media & mask) == (target & mask)) { |
| #if defined(IFMEDIA_DEBUG) || defined(DIAGNOSTIC) |
| if (match) |
| printk("ifmedia_match: multiple match for " |
| "0x%x/0x%x\n", target, mask); |
| #endif |
| match = next; |
| } |
| } |
| |
| return match; |
| } |
| |
| #ifdef IFMEDIA_DEBUG |
| struct ifmedia_description ifm_type_descriptions[] = |
| IFM_TYPE_DESCRIPTIONS; |
| |
| struct ifmedia_description ifm_subtype_ethernet_descriptions[] = |
| IFM_SUBTYPE_ETHERNET_DESCRIPTIONS; |
| |
| struct ifmedia_description ifm_subtype_ethernet_option_descriptions[] = |
| IFM_SUBTYPE_ETHERNET_OPTION_DESCRIPTIONS; |
| |
| struct ifmedia_description ifm_subtype_tokenring_descriptions[] = |
| IFM_SUBTYPE_TOKENRING_DESCRIPTIONS; |
| |
| struct ifmedia_description ifm_subtype_tokenring_option_descriptions[] = |
| IFM_SUBTYPE_TOKENRING_OPTION_DESCRIPTIONS; |
| |
| struct ifmedia_description ifm_subtype_fddi_descriptions[] = |
| IFM_SUBTYPE_FDDI_DESCRIPTIONS; |
| |
| struct ifmedia_description ifm_subtype_fddi_option_descriptions[] = |
| IFM_SUBTYPE_FDDI_OPTION_DESCRIPTIONS; |
| |
| struct ifmedia_description ifm_subtype_ieee80211_descriptions[] = |
| IFM_SUBTYPE_IEEE80211_DESCRIPTIONS; |
| |
| struct ifmedia_description ifm_subtype_ieee80211_option_descriptions[] = |
| IFM_SUBTYPE_IEEE80211_OPTION_DESCRIPTIONS; |
| |
| struct ifmedia_description ifm_subtype_ieee80211_mode_descriptions[] = |
| IFM_SUBTYPE_IEEE80211_MODE_DESCRIPTIONS; |
| |
| struct ifmedia_description ifm_subtype_shared_descriptions[] = |
| IFM_SUBTYPE_SHARED_DESCRIPTIONS; |
| |
| struct ifmedia_description ifm_shared_option_descriptions[] = |
| IFM_SHARED_OPTION_DESCRIPTIONS; |
| |
| struct ifmedia_type_to_subtype { /* XXX: right place for declaration? */ |
| struct ifmedia_description *subtypes; |
| struct ifmedia_description *options; |
| struct ifmedia_description *modes; |
| }; |
| |
| /* must be in the same order as IFM_TYPE_DESCRIPTIONS */ |
| struct ifmedia_type_to_subtype ifmedia_types_to_subtypes[] = { |
| { &ifm_subtype_ethernet_descriptions[0], |
| &ifm_subtype_ethernet_option_descriptions[0], |
| NULL, }, |
| { &ifm_subtype_tokenring_descriptions[0], |
| &ifm_subtype_tokenring_option_descriptions[0], |
| NULL, }, |
| { &ifm_subtype_fddi_descriptions[0], |
| &ifm_subtype_fddi_option_descriptions[0], |
| NULL, }, |
| { &ifm_subtype_ieee80211_descriptions[0], |
| &ifm_subtype_ieee80211_option_descriptions[0], |
| &ifm_subtype_ieee80211_mode_descriptions[0] }, |
| }; |
| |
| /* |
| * print a media word. |
| */ |
| static void |
| ifmedia_printword(int ifmw) |
| { |
| struct ifmedia_description *desc; |
| struct ifmedia_type_to_subtype *ttos; |
| int seen_option = 0; |
| |
| /* Find the top-level interface type. */ |
| for (desc = ifm_type_descriptions, ttos = ifmedia_types_to_subtypes; |
| desc->ifmt_string != NULL; desc++, ttos++) |
| if (IFM_TYPE(ifmw) == desc->ifmt_word) |
| break; |
| if (desc->ifmt_string == NULL) { |
| printk("<unknown type>\n"); |
| return; |
| } |
| printk(desc->ifmt_string); |
| |
| /* Any mode. */ |
| for (desc = ttos->modes; desc && desc->ifmt_string != NULL; desc++) |
| if (IFM_MODE(ifmw) == desc->ifmt_word) { |
| if (desc->ifmt_string != NULL) |
| printk(" mode %s", desc->ifmt_string); |
| break; |
| } |
| |
| /* |
| * Check for the shared subtype descriptions first, then the |
| * type-specific ones. |
| */ |
| for (desc = ifm_subtype_shared_descriptions; |
| desc->ifmt_string != NULL; desc++) |
| if (IFM_SUBTYPE(ifmw) == desc->ifmt_word) |
| goto got_subtype; |
| |
| for (desc = ttos->subtypes; desc->ifmt_string != NULL; desc++) |
| if (IFM_SUBTYPE(ifmw) == desc->ifmt_word) |
| break; |
| if (desc->ifmt_string == NULL) { |
| printk(" <unknown subtype>\n"); |
| return; |
| } |
| |
| got_subtype: |
| printk(" %s", desc->ifmt_string); |
| |
| /* |
| * Look for shared options. |
| */ |
| for (desc = ifm_shared_option_descriptions; |
| desc->ifmt_string != NULL; desc++) { |
| if (ifmw & desc->ifmt_word) { |
| if (seen_option == 0) |
| printk(" <"); |
| printk("%s%s", seen_option++ ? "," : "", |
| desc->ifmt_string); |
| } |
| } |
| |
| /* |
| * Look for subtype-specific options. |
| */ |
| for (desc = ttos->options; desc->ifmt_string != NULL; desc++) { |
| if (ifmw & desc->ifmt_word) { |
| if (seen_option == 0) |
| printk(" <"); |
| printk("%s%s", seen_option++ ? "," : "", |
| desc->ifmt_string); |
| } |
| } |
| printk("%s\n", seen_option ? ">" : ""); |
| } |
| #endif /* IFMEDIA_DEBUG */ |
| |
| EXPORT_SYMBOL(ifmedia_ioctl); |