/* * Copyright (c) 2002-2009 Sam Leffler, Errno Consulting * Copyright (c) 2005-2006 Atheros Communications, Inc. * All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $FreeBSD$ */ #include "opt_ah.h" #include "ah.h" #include #include #include "ah_internal.h" #include "ah_eeprom.h" #include "ah_devid.h" #include "ah_regdomain.h" /* * XXX this code needs a audit+review */ /* used throughout this file... */ #define N(a) nitems(a) #define HAL_MODE_11A_TURBO HAL_MODE_108A #define HAL_MODE_11G_TURBO HAL_MODE_108G /* * Mask to check whether a domain is a multidomain or a single domain */ #define MULTI_DOMAIN_MASK 0xFF00 /* * Enumerated Regulatory Domain Information 8 bit values indicate that * the regdomain is really a pair of unitary regdomains. 12 bit values * are the real unitary regdomains and are the only ones which have the * frequency bitmasks and flags set. */ #include "ah_regdomain/ah_rd_regenum.h" #define WORLD_SKU_MASK 0x00F0 #define WORLD_SKU_PREFIX 0x0060 /* * THE following table is the mapping of regdomain pairs specified by * an 8 bit regdomain value to the individual unitary reg domains */ #include "ah_regdomain/ah_rd_regmap.h" /* * The following tables are the master list for all different freqeuncy * bands with the complete matrix of all possible flags and settings * for each band if it is used in ANY reg domain. */ #define COUNTRY_ERD_FLAG 0x8000 #define WORLDWIDE_ROAMING_FLAG 0x4000 /* * This table maps country ISO codes from net80211 into regulatory * domains which the ath regulatory domain code understands. */ #include "ah_regdomain/ah_rd_ctry.h" /* * The frequency band collections are a set of frequency ranges * with shared properties - max tx power, max antenna gain, channel width, * channel spacing, DFS requirements and passive scanning requirements. * * These are represented as entries in a frequency band bitmask. * Each regulatory domain entry in ah_regdomain_domains.h uses one * or more frequency band entries for each of the channel modes * supported (11bg, 11a, half, quarter, turbo, etc.) * */ #include "ah_regdomain/ah_rd_freqbands.h" /* * This is the main regulatory database. It defines the supported * set of features and requirements for each of the defined regulatory * zones. It uses combinations of frequency ranges - represented in * a bitmask - to determine the requirements and limitations needed. */ #include "ah_regdomain/ah_rd_domains.h" static const struct cmode modes[] = { { HAL_MODE_TURBO, IEEE80211_CHAN_ST, ®Dmn5GhzTurboFreq[0] }, { HAL_MODE_11A, IEEE80211_CHAN_A, ®Dmn5GhzFreq[0] }, { HAL_MODE_11B, IEEE80211_CHAN_B, ®Dmn2GhzFreq[0] }, { HAL_MODE_11G, IEEE80211_CHAN_G, ®Dmn2Ghz11gFreq[0] }, { HAL_MODE_11G_TURBO, IEEE80211_CHAN_108G, ®Dmn2Ghz11gTurboFreq[0] }, { HAL_MODE_11A_TURBO, IEEE80211_CHAN_108A, ®Dmn5GhzTurboFreq[0] }, { HAL_MODE_11A_QUARTER_RATE, IEEE80211_CHAN_A | IEEE80211_CHAN_QUARTER, ®Dmn5GhzFreq[0] }, { HAL_MODE_11A_HALF_RATE, IEEE80211_CHAN_A | IEEE80211_CHAN_HALF, ®Dmn5GhzFreq[0] }, { HAL_MODE_11G_QUARTER_RATE, IEEE80211_CHAN_G | IEEE80211_CHAN_QUARTER, ®Dmn2Ghz11gFreq[0] }, { HAL_MODE_11G_HALF_RATE, IEEE80211_CHAN_G | IEEE80211_CHAN_HALF, ®Dmn2Ghz11gFreq[0] }, { HAL_MODE_11NG_HT20, IEEE80211_CHAN_G | IEEE80211_CHAN_HT20, ®Dmn2Ghz11gFreq[0] }, { HAL_MODE_11NG_HT40PLUS, IEEE80211_CHAN_G | IEEE80211_CHAN_HT40U, ®Dmn2Ghz11gFreq[0] }, { HAL_MODE_11NG_HT40MINUS, IEEE80211_CHAN_G | IEEE80211_CHAN_HT40D, ®Dmn2Ghz11gFreq[0] }, { HAL_MODE_11NA_HT20, IEEE80211_CHAN_A | IEEE80211_CHAN_HT20, ®Dmn5GhzFreq[0] }, { HAL_MODE_11NA_HT40PLUS, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40U, ®Dmn5GhzFreq[0] }, { HAL_MODE_11NA_HT40MINUS, IEEE80211_CHAN_A | IEEE80211_CHAN_HT40D, ®Dmn5GhzFreq[0] }, }; static void ath_hal_update_dfsdomain(struct ath_hal *ah); static OS_INLINE uint16_t getEepromRD(struct ath_hal *ah) { return AH_PRIVATE(ah)->ah_currentRD &~ WORLDWIDE_ROAMING_FLAG; } /* * Test to see if the bitmask array is all zeros */ static HAL_BOOL isChanBitMaskZero(const uint64_t *bitmask) { #if BMLEN > 2 #error "add more cases" #endif #if BMLEN > 1 if (bitmask[1] != 0) return AH_FALSE; #endif return (bitmask[0] == 0); } /* * Return whether or not the regulatory domain/country in EEPROM * is acceptable. */ static HAL_BOOL isEepromValid(struct ath_hal *ah) { uint16_t rd = getEepromRD(ah); int i; if (rd & COUNTRY_ERD_FLAG) { uint16_t cc = rd &~ COUNTRY_ERD_FLAG; for (i = 0; i < N(allCountries); i++) if (allCountries[i].countryCode == cc) return AH_TRUE; } else { for (i = 0; i < N(regDomainPairs); i++) if (regDomainPairs[i].regDmnEnum == rd) return AH_TRUE; } if (rd == FCC_UBNT) { return AH_TRUE; } HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: invalid regulatory domain/country code 0x%x\n", __func__, rd); return AH_FALSE; } /* * Find the pointer to the country element in the country table * corresponding to the country code */ static COUNTRY_CODE_TO_ENUM_RD* findCountry(HAL_CTRY_CODE countryCode) { int i; for (i = 0; i < N(allCountries); i++) { if (allCountries[i].countryCode == countryCode) return &allCountries[i]; } return AH_NULL; } static REG_DOMAIN * findRegDmn(int regDmn) { int i; for (i = 0; i < N(regDomains); i++) { if (regDomains[i].regDmnEnum == regDmn) return ®Domains[i]; } return AH_NULL; } static REG_DMN_PAIR_MAPPING * findRegDmnPair(int regDmnPair) { int i; if (regDmnPair != NO_ENUMRD) { for (i = 0; i < N(regDomainPairs); i++) { if (regDomainPairs[i].regDmnEnum == regDmnPair) return ®DomainPairs[i]; } } return AH_NULL; } /* * Calculate a default country based on the EEPROM setting. */ static HAL_CTRY_CODE getDefaultCountry(struct ath_hal *ah) { REG_DMN_PAIR_MAPPING *regpair; uint16_t rd; rd = getEepromRD(ah); if (rd & COUNTRY_ERD_FLAG) { COUNTRY_CODE_TO_ENUM_RD *country; uint16_t cc = rd & ~COUNTRY_ERD_FLAG; country = findCountry(cc); if (country != AH_NULL) return cc; } /* * Check reg domains that have only one country */ regpair = findRegDmnPair(rd); return (regpair != AH_NULL) ? regpair->singleCC : CTRY_DEFAULT; } static HAL_BOOL IS_BIT_SET(int bit, const uint64_t bitmask[]) { int byteOffset, bitnum; uint64_t val; byteOffset = bit/64; bitnum = bit - byteOffset*64; val = ((uint64_t) 1) << bitnum; return (bitmask[byteOffset] & val) != 0; } static HAL_STATUS getregstate(struct ath_hal *ah, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn, COUNTRY_CODE_TO_ENUM_RD **pcountry, REG_DOMAIN **prd2GHz, REG_DOMAIN **prd5GHz) { COUNTRY_CODE_TO_ENUM_RD *country; REG_DOMAIN *rd5GHz, *rd2GHz; if (cc == CTRY_DEFAULT && regDmn == SKU_NONE) { /* * Validate the EEPROM setting and setup defaults */ if (!isEepromValid(ah)) { /* * Don't return any channels if the EEPROM has an * invalid regulatory domain/country code setting. */ HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: invalid EEPROM contents\n",__func__); return HAL_EEBADREG; } cc = getDefaultCountry(ah); country = findCountry(cc); if (country == AH_NULL) { HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "NULL Country!, cc %d\n", cc); return HAL_EEBADCC; } regDmn = country->regDmnEnum; HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: EEPROM cc %u rd 0x%x\n", __func__, cc, regDmn); if (country->countryCode == CTRY_DEFAULT) { /* * Check EEPROM; SKU may be for a country, single * domain, or multiple domains (WWR). */ uint16_t rdnum = getEepromRD(ah); if ((rdnum & COUNTRY_ERD_FLAG) == 0 && (findRegDmn(rdnum) != AH_NULL || findRegDmnPair(rdnum) != AH_NULL)) { regDmn = rdnum; HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: EEPROM rd 0x%x\n", __func__, rdnum); } } } else { country = findCountry(cc); if (country == AH_NULL) { HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "unknown country, cc %d\n", cc); return HAL_EINVAL; } if (regDmn == SKU_NONE) regDmn = country->regDmnEnum; HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u rd 0x%x\n", __func__, cc, regDmn); } /* * Setup per-band state. */ if ((regDmn & MULTI_DOMAIN_MASK) == 0) { REG_DMN_PAIR_MAPPING *regpair = findRegDmnPair(regDmn); if (regpair == AH_NULL) { HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: no reg domain pair %u for country %u\n", __func__, regDmn, country->countryCode); return HAL_EINVAL; } rd5GHz = findRegDmn(regpair->regDmn5GHz); if (rd5GHz == AH_NULL) { HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: no 5GHz reg domain %u for country %u\n", __func__, regpair->regDmn5GHz, country->countryCode); return HAL_EINVAL; } rd2GHz = findRegDmn(regpair->regDmn2GHz); if (rd2GHz == AH_NULL) { HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: no 2GHz reg domain %u for country %u\n", __func__, regpair->regDmn2GHz, country->countryCode); return HAL_EINVAL; } } else { rd5GHz = rd2GHz = findRegDmn(regDmn); if (rd2GHz == AH_NULL) { HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: no unitary reg domain %u for country %u\n", __func__, regDmn, country->countryCode); return HAL_EINVAL; } } if (pcountry != AH_NULL) *pcountry = country; *prd2GHz = rd2GHz; *prd5GHz = rd5GHz; return HAL_OK; } static uint64_t * getchannelBM(u_int mode, REG_DOMAIN *rd) { switch (mode) { case HAL_MODE_11B: return (rd->chan11b); case HAL_MODE_11G_QUARTER_RATE: return (rd->chan11g_quarter); case HAL_MODE_11G_HALF_RATE: return (rd->chan11g_half); case HAL_MODE_11G: case HAL_MODE_11NG_HT20: case HAL_MODE_11NG_HT40PLUS: case HAL_MODE_11NG_HT40MINUS: return (rd->chan11g); case HAL_MODE_11G_TURBO: return (rd->chan11g_turbo); case HAL_MODE_11A_QUARTER_RATE: return (rd->chan11a_quarter); case HAL_MODE_11A_HALF_RATE: return (rd->chan11a_half); case HAL_MODE_11A: case HAL_MODE_11NA_HT20: case HAL_MODE_11NA_HT40PLUS: case HAL_MODE_11NA_HT40MINUS: return (rd->chan11a); case HAL_MODE_TURBO: return (rd->chan11a_turbo); case HAL_MODE_11A_TURBO: return (rd->chan11a_dyn_turbo); default: return (AH_NULL); } } static void setchannelflags(struct ieee80211_channel *c, REG_DMN_FREQ_BAND *fband, REG_DOMAIN *rd) { if (fband->usePassScan & rd->pscan) c->ic_flags |= IEEE80211_CHAN_PASSIVE; if (fband->useDfs & rd->dfsMask) c->ic_flags |= IEEE80211_CHAN_DFS; if (IEEE80211_IS_CHAN_5GHZ(c) && (rd->flags & DISALLOW_ADHOC_11A)) c->ic_flags |= IEEE80211_CHAN_NOADHOC; if (IEEE80211_IS_CHAN_TURBO(c) && (rd->flags & DISALLOW_ADHOC_11A_TURB)) c->ic_flags |= IEEE80211_CHAN_NOADHOC; if (rd->flags & NO_HOSTAP) c->ic_flags |= IEEE80211_CHAN_NOHOSTAP; if (rd->flags & LIMIT_FRAME_4MS) c->ic_flags |= IEEE80211_CHAN_4MSXMIT; if (rd->flags & NEED_NFC) c->ic_flags |= CHANNEL_NFCREQUIRED; } static int addchan(struct ath_hal *ah, struct ieee80211_channel chans[], u_int maxchans, int *nchans, uint16_t freq, uint32_t flags, REG_DMN_FREQ_BAND *fband, REG_DOMAIN *rd) { struct ieee80211_channel *c; if (*nchans >= maxchans) return (HAL_ENOMEM); c = &chans[(*nchans)++]; c->ic_freq = freq; c->ic_flags = flags; setchannelflags(c, fband, rd); c->ic_maxregpower = fband->powerDfs; ath_hal_getpowerlimits(ah, c); c->ic_maxantgain = fband->antennaMax; return (0); } static int copychan_prev(struct ath_hal *ah, struct ieee80211_channel chans[], u_int maxchans, int *nchans, uint16_t freq) { struct ieee80211_channel *c; if (*nchans == 0) return (HAL_EINVAL); if (*nchans >= maxchans) return (HAL_ENOMEM); c = &chans[(*nchans)++]; c[0] = c[-1]; c->ic_freq = freq; /* XXX is it needed here? */ ath_hal_getpowerlimits(ah, c); return (0); } static int add_chanlist_band(struct ath_hal *ah, struct ieee80211_channel chans[], int maxchans, int *nchans, uint16_t freq_lo, uint16_t freq_hi, int step, uint32_t flags, REG_DMN_FREQ_BAND *fband, REG_DOMAIN *rd) { uint16_t freq = freq_lo; int error; if (freq_hi < freq_lo) return (0); error = addchan(ah, chans, maxchans, nchans, freq, flags, fband, rd); for (freq += step; freq <= freq_hi && error == 0; freq += step) error = copychan_prev(ah, chans, maxchans, nchans, freq); return (error); } static void adj_freq_ht40(u_int mode, int *low_adj, int *hi_adj, int *channelSep) { *low_adj = *hi_adj = *channelSep = 0; switch (mode) { case HAL_MODE_11NA_HT40PLUS: *channelSep = 40; /* FALLTHROUGH */ case HAL_MODE_11NG_HT40PLUS: *hi_adj = -20; break; case HAL_MODE_11NA_HT40MINUS: *channelSep = 40; /* FALLTHROUGH */ case HAL_MODE_11NG_HT40MINUS: *low_adj = 20; break; } } static void add_chanlist_mode(struct ath_hal *ah, struct ieee80211_channel chans[], u_int maxchans, int *nchans, const struct cmode *cm, REG_DOMAIN *rd, HAL_BOOL enableExtendedChannels) { uint64_t *channelBM; uint16_t freq_lo, freq_hi; int b, error, low_adj, hi_adj, channelSep; if (!ath_hal_getChannelEdges(ah, cm->flags, &freq_lo, &freq_hi)) { /* channel not supported by hardware, skip it */ HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: channels 0x%x not supported by hardware\n", __func__, cm->flags); return; } channelBM = getchannelBM(cm->mode, rd); if (isChanBitMaskZero(channelBM)) return; /* * Setup special handling for HT40 channels; e.g. * 5G HT40 channels require 40Mhz channel separation. */ adj_freq_ht40(cm->mode, &low_adj, &hi_adj, &channelSep); for (b = 0; b < 64*BMLEN; b++) { REG_DMN_FREQ_BAND *fband; uint16_t bfreq_lo, bfreq_hi; int step; if (!IS_BIT_SET(b, channelBM)) continue; fband = &cm->freqs[b]; if ((fband->usePassScan & IS_ECM_CHAN) && !enableExtendedChannels) { HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "skip ecm channels\n"); continue; } #if 0 if ((fband->useDfs & rd->dfsMask) && (cm->flags & IEEE80211_CHAN_HT40)) { /* NB: DFS and HT40 don't mix */ HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "skip HT40 chan, DFS required\n"); continue; } #endif /* * XXX TODO: handle REG_EXT_FCC_CH_144. * * Figure out which instances/uses cause us to not * be allowed to use channel 144 (pri or sec overlap.) */ bfreq_lo = MAX(fband->lowChannel + low_adj, freq_lo); bfreq_hi = MIN(fband->highChannel + hi_adj, freq_hi); if (fband->channelSep >= channelSep) step = fband->channelSep; else step = roundup(channelSep, fband->channelSep); error = add_chanlist_band(ah, chans, maxchans, nchans, bfreq_lo, bfreq_hi, step, cm->flags, fband, rd); if (error != 0) { HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: too many channels for channel table\n", __func__); return; } } } static u_int getmodesmask(struct ath_hal *ah, REG_DOMAIN *rd5GHz, u_int modeSelect) { #define HAL_MODE_11A_ALL \ (HAL_MODE_11A | HAL_MODE_11A_TURBO | HAL_MODE_TURBO | \ HAL_MODE_11A_QUARTER_RATE | HAL_MODE_11A_HALF_RATE) u_int modesMask; /* get modes that HW is capable of */ modesMask = ath_hal_getWirelessModes(ah); modesMask &= modeSelect; /* optimize work below if no 11a channels */ if (isChanBitMaskZero(rd5GHz->chan11a) && (modesMask & HAL_MODE_11A_ALL)) { HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: disallow all 11a\n", __func__); modesMask &= ~HAL_MODE_11A_ALL; } return (modesMask); #undef HAL_MODE_11A_ALL } /* * Construct the channel list for the specified regulatory config. */ static HAL_STATUS getchannels(struct ath_hal *ah, struct ieee80211_channel chans[], u_int maxchans, int *nchans, u_int modeSelect, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn, HAL_BOOL enableExtendedChannels, COUNTRY_CODE_TO_ENUM_RD **pcountry, REG_DOMAIN **prd2GHz, REG_DOMAIN **prd5GHz) { REG_DOMAIN *rd5GHz, *rd2GHz; u_int modesMask; const struct cmode *cm; HAL_STATUS status; HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u regDmn 0x%x mode 0x%x%s\n", __func__, cc, regDmn, modeSelect, enableExtendedChannels ? " ecm" : ""); status = getregstate(ah, cc, regDmn, pcountry, &rd2GHz, &rd5GHz); if (status != HAL_OK) return status; modesMask = getmodesmask(ah, rd5GHz, modeSelect); /* XXX error? */ if (modesMask == 0) goto done; for (cm = modes; cm < &modes[N(modes)]; cm++) { REG_DOMAIN *rd; if ((cm->mode & modesMask) == 0) { HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: skip mode 0x%x flags 0x%x\n", __func__, cm->mode, cm->flags); continue; } if (cm->flags & IEEE80211_CHAN_5GHZ) rd = rd5GHz; else if (cm->flags & IEEE80211_CHAN_2GHZ) rd = rd2GHz; else { ath_hal_printf(ah, "%s: Unkonwn HAL flags 0x%x\n", __func__, cm->flags); return HAL_EINVAL; } add_chanlist_mode(ah, chans, maxchans, nchans, cm, rd, enableExtendedChannels); if (*nchans >= maxchans) goto done; } done: /* NB: pcountry set above by getregstate */ if (prd2GHz != AH_NULL) *prd2GHz = rd2GHz; if (prd5GHz != AH_NULL) *prd5GHz = rd5GHz; return HAL_OK; } /* * Retrieve a channel list without affecting runtime state. */ HAL_STATUS ath_hal_getchannels(struct ath_hal *ah, struct ieee80211_channel chans[], u_int maxchans, int *nchans, u_int modeSelect, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn, HAL_BOOL enableExtendedChannels) { return getchannels(ah, chans, maxchans, nchans, modeSelect, cc, regDmn, enableExtendedChannels, AH_NULL, AH_NULL, AH_NULL); } /* * Handle frequency mapping from 900Mhz range to 2.4GHz range * for GSM radios. This is done when we need the h/w frequency * and the channel is marked IEEE80211_CHAN_GSM. */ static int ath_hal_mapgsm(int sku, int freq) { if (sku == SKU_XR9) return 1520 + freq; if (sku == SKU_GZ901) return 1544 + freq; if (sku == SKU_SR9) return 3344 - freq; if (sku == SKU_XC900M) return 1517 + freq; HALDEBUG(AH_NULL, HAL_DEBUG_ANY, "%s: cannot map freq %u unknown gsm sku %u\n", __func__, freq, sku); return freq; } /* * Setup the internal/private channel state given a table of * net80211 channels. We collapse entries for the same frequency * and record the frequency for doing noise floor processing * where we don't have net80211 channel context. */ static HAL_BOOL assignPrivateChannels(struct ath_hal *ah, struct ieee80211_channel chans[], int nchans, int sku) { HAL_CHANNEL_INTERNAL *ic; int i, j, next, freq; next = 0; for (i = 0; i < nchans; i++) { struct ieee80211_channel *c = &chans[i]; for (j = i-1; j >= 0; j--) if (chans[j].ic_freq == c->ic_freq) { c->ic_devdata = chans[j].ic_devdata; break; } if (j < 0) { /* new entry, assign a private channel entry */ if (next >= N(AH_PRIVATE(ah)->ah_channels)) { HALDEBUG(ah, HAL_DEBUG_ANY, "%s: too many channels, max %zu\n", __func__, N(AH_PRIVATE(ah)->ah_channels)); return AH_FALSE; } /* * Handle frequency mapping for 900MHz devices. * The hardware uses 2.4GHz frequencies that are * down-converted. The 802.11 layer uses the * true frequencies. */ freq = IEEE80211_IS_CHAN_GSM(c) ? ath_hal_mapgsm(sku, c->ic_freq) : c->ic_freq; HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: private[%3u] %u/0x%x -> channel %u\n", __func__, next, c->ic_freq, c->ic_flags, freq); ic = &AH_PRIVATE(ah)->ah_channels[next]; /* * NB: This clears privFlags which means ancillary * code like ANI and IQ calibration will be * restarted and re-setup any per-channel state. */ OS_MEMZERO(ic, sizeof(*ic)); ic->channel = freq; c->ic_devdata = next; next++; } } AH_PRIVATE(ah)->ah_nchan = next; HALDEBUG(ah, HAL_DEBUG_ANY, "%s: %u public, %u private channels\n", __func__, nchans, next); return AH_TRUE; } /* * Setup the channel list based on the information in the EEPROM. */ HAL_STATUS ath_hal_init_channels(struct ath_hal *ah, struct ieee80211_channel chans[], u_int maxchans, int *nchans, u_int modeSelect, HAL_CTRY_CODE cc, HAL_REG_DOMAIN regDmn, HAL_BOOL enableExtendedChannels) { COUNTRY_CODE_TO_ENUM_RD *country; REG_DOMAIN *rd5GHz, *rd2GHz; HAL_STATUS status; status = getchannels(ah, chans, maxchans, nchans, modeSelect, cc, regDmn, enableExtendedChannels, &country, &rd2GHz, &rd5GHz); if (status == HAL_OK && assignPrivateChannels(ah, chans, *nchans, AH_PRIVATE(ah)->ah_currentRD)) { AH_PRIVATE(ah)->ah_rd2GHz = rd2GHz; AH_PRIVATE(ah)->ah_rd5GHz = rd5GHz; ah->ah_countryCode = country->countryCode; HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u\n", __func__, ah->ah_countryCode); /* Update current DFS domain */ ath_hal_update_dfsdomain(ah); } else status = HAL_EINVAL; return status; } /* * Set the channel list. */ HAL_STATUS ath_hal_set_channels(struct ath_hal *ah, struct ieee80211_channel chans[], int nchans, HAL_CTRY_CODE cc, HAL_REG_DOMAIN rd) { COUNTRY_CODE_TO_ENUM_RD *country; REG_DOMAIN *rd5GHz, *rd2GHz; HAL_STATUS status; switch (rd) { case SKU_SR9: case SKU_XR9: case SKU_GZ901: case SKU_XC900M: /* * Map 900MHz sku's. The frequencies will be mapped * according to the sku to compensate for the down-converter. * We use the FCC for these sku's as the mapped channel * list is known compatible (will need to change if/when * vendors do different mapping in different locales). */ status = getregstate(ah, CTRY_DEFAULT, SKU_FCC, &country, &rd2GHz, &rd5GHz); break; default: status = getregstate(ah, cc, rd, &country, &rd2GHz, &rd5GHz); rd = AH_PRIVATE(ah)->ah_currentRD; break; } if (status == HAL_OK && assignPrivateChannels(ah, chans, nchans, rd)) { AH_PRIVATE(ah)->ah_rd2GHz = rd2GHz; AH_PRIVATE(ah)->ah_rd5GHz = rd5GHz; ah->ah_countryCode = country->countryCode; HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s: cc %u\n", __func__, ah->ah_countryCode); } else status = HAL_EINVAL; if (status == HAL_OK) { /* Update current DFS domain */ (void) ath_hal_update_dfsdomain(ah); } return status; } #ifdef AH_DEBUG /* * Return the internal channel corresponding to a public channel. * NB: normally this routine is inline'd (see ah_internal.h) */ HAL_CHANNEL_INTERNAL * ath_hal_checkchannel(struct ath_hal *ah, const struct ieee80211_channel *c) { HAL_CHANNEL_INTERNAL *cc = &AH_PRIVATE(ah)->ah_channels[c->ic_devdata]; if (c->ic_devdata < AH_PRIVATE(ah)->ah_nchan && (c->ic_freq == cc->channel || IEEE80211_IS_CHAN_GSM(c))) return cc; if (c->ic_devdata >= AH_PRIVATE(ah)->ah_nchan) { HALDEBUG(ah, HAL_DEBUG_ANY, "%s: bad mapping, devdata %u nchans %u\n", __func__, c->ic_devdata, AH_PRIVATE(ah)->ah_nchan); HALASSERT(c->ic_devdata < AH_PRIVATE(ah)->ah_nchan); } else { HALDEBUG(ah, HAL_DEBUG_ANY, "%s: no match for %u/0x%x devdata %u channel %u\n", __func__, c->ic_freq, c->ic_flags, c->ic_devdata, cc->channel); HALASSERT(c->ic_freq == cc->channel || IEEE80211_IS_CHAN_GSM(c)); } return AH_NULL; } #endif /* AH_DEBUG */ #define isWwrSKU(_ah) \ ((getEepromRD((_ah)) & WORLD_SKU_MASK) == WORLD_SKU_PREFIX || \ getEepromRD(_ah) == WORLD) /* * Return the test group for the specific channel based on * the current regulatory setup. */ u_int ath_hal_getctl(struct ath_hal *ah, const struct ieee80211_channel *c) { u_int ctl; if (AH_PRIVATE(ah)->ah_rd2GHz == AH_PRIVATE(ah)->ah_rd5GHz || (ah->ah_countryCode == CTRY_DEFAULT && isWwrSKU(ah))) ctl = SD_NO_CTL; else if (IEEE80211_IS_CHAN_2GHZ(c)) ctl = AH_PRIVATE(ah)->ah_rd2GHz->conformanceTestLimit; else ctl = AH_PRIVATE(ah)->ah_rd5GHz->conformanceTestLimit; if (IEEE80211_IS_CHAN_B(c)) return ctl | CTL_11B; if (IEEE80211_IS_CHAN_G(c)) return ctl | CTL_11G; if (IEEE80211_IS_CHAN_108G(c)) return ctl | CTL_108G; if (IEEE80211_IS_CHAN_TURBO(c)) return ctl | CTL_TURBO; if (IEEE80211_IS_CHAN_A(c)) return ctl | CTL_11A; return ctl; } /* * Update the current dfsDomain setting based on the given * country code. * * Since FreeBSD/net80211 allows the channel set to change * after the card has been setup (via ath_hal_init_channels()) * this function method is needed to update ah_dfsDomain. */ void ath_hal_update_dfsdomain(struct ath_hal *ah) { const REG_DOMAIN *rd5GHz = AH_PRIVATE(ah)->ah_rd5GHz; HAL_DFS_DOMAIN dfsDomain = HAL_DFS_UNINIT_DOMAIN; if (rd5GHz->dfsMask & DFS_FCC3) dfsDomain = HAL_DFS_FCC_DOMAIN; if (rd5GHz->dfsMask & DFS_ETSI) dfsDomain = HAL_DFS_ETSI_DOMAIN; if (rd5GHz->dfsMask & DFS_MKK4) dfsDomain = HAL_DFS_MKK4_DOMAIN; AH_PRIVATE(ah)->ah_dfsDomain = dfsDomain; HALDEBUG(ah, HAL_DEBUG_REGDOMAIN, "%s ah_dfsDomain: %d\n", __func__, AH_PRIVATE(ah)->ah_dfsDomain); } /* * Return the max allowed antenna gain and apply any regulatory * domain specific changes. * * NOTE: a negative reduction is possible in RD's that only * measure radiated power (e.g., ETSI) which would increase * that actual conducted output power (though never beyond * the calibrated target power). */ u_int ath_hal_getantennareduction(struct ath_hal *ah, const struct ieee80211_channel *chan, u_int twiceGain) { int8_t antennaMax = twiceGain - chan->ic_maxantgain*2; return (antennaMax < 0) ? 0 : antennaMax; }