/*
Broadcom BCM43xx wireless driver
Transmission (TX/RX) related functions.
Copyright (c) 2005 Martin Langer <martin-langer@gmx.de>,
Stefano Brivio <st3@riseup.net>
Michael Buesch <mbuesch@freenet.de>
Danny van Dyk <kugelfang@gentoo.org>
Andreas Jaggi <andreas.jaggi@waterwave.ch>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "bcm43xx_xmit.h"
#include <linux/etherdevice.h>
/* Extract the bitrate out of a CCK PLCP header. */
static u8 bcm43xx_plcp_get_bitrate_cck(struct bcm43xx_plcp_hdr4 *plcp)
{
switch (plcp->raw[0]) {
case 0x0A:
return IEEE80211_CCK_RATE_1MB;
case 0x14:
return IEEE80211_CCK_RATE_2MB;
case 0x37:
return IEEE80211_CCK_RATE_5MB;
case 0x6E:
return IEEE80211_CCK_RATE_11MB;
}
assert(0);
return 0;
}
/* Extract the bitrate out of an OFDM PLCP header. */
static u8 bcm43xx_plcp_get_bitrate_ofdm(struct bcm43xx_plcp_hdr4 *plcp)
{
switch (plcp->raw[0] & 0xF) {
case 0xB:
return IEEE80211_OFDM_RATE_6MB;
case 0xF:
return IEEE80211_OFDM_RATE_9MB;
case 0xA:
return IEEE80211_OFDM_RATE_12MB;
case 0xE:
return IEEE80211_OFDM_RATE_18MB;
case 0x9:
return IEEE80211_OFDM_RATE_24MB;
case 0xD:
return IEEE80211_OFDM_RATE_36MB;
case 0x8:
return IEEE80211_OFDM_RATE_48MB;
case 0xC:
return IEEE80211_OFDM_RATE_54MB;
}
assert(0);
return 0;
}
u8 bcm43xx_plcp_get_ratecode_cck(const u8 bitrate)
{
switch (bitrate) {
case IEEE80211_CCK_RATE_1MB:
return 0x0A;
case IEEE80211_CCK_RATE_2MB:
return 0x14;
case IEEE80211_CCK_RATE_5MB:
return 0x37;
case IEEE80211_CCK_RATE_11MB:
return 0x6E;
}
assert(0);
return 0;
}
u8 bcm43xx_plcp_get_ratecode_ofdm(const u8 bitrate)
{
switch (bitrate) {
case IEEE80211_OFDM_RATE_6MB:
return 0xB;
case IEEE80211_OFDM_RATE_9MB:
return 0xF;
case IEEE80211_OFDM_RATE_12MB:
return 0xA;
case IEEE80211_OFDM_RATE_18MB:
return 0xE;
case IEEE80211_OFDM_RATE_24MB:
return 0x9;
case IEEE80211_OFDM_RATE_36MB:
return 0xD;
case IEEE80211_OFDM_RATE_48MB:
return 0x8;
case IEEE80211_OFDM_RATE_54MB:
return 0xC;
}
assert(0);
return 0;
}
static void bcm43xx_generate_plcp_hdr(struct bcm43xx_plcp_hdr4 *plcp,
const u16 octets, const u8 bitrate,
const int ofdm_modulation)
{
__le32 *data = &(plcp->data);
__u8 *raw = plcp->raw;
if (ofdm_modulation) {
u32 val = bcm43xx_plcp_get_ratecode_ofdm(bitrate);
assert(!(octets & 0xF000));
val |= (octets << 5);
*data = cpu_to_le32(val);
} else {
u32 plen;
plen = octets * 16 / bitrate;
if ((octets * 16 % bitrate) > 0) {
plen++;
if ((bitrate == IEEE80211_CCK_RATE_11MB)
&& ((octets * 8 % 11) < 4)) {
raw[1] = 0x84;
} else
raw[1] = 0x04;
} else
raw[1] = 0x04;
*data |= cpu_to_le32(plen << 16);
raw[0] = bcm43xx_plcp_get_ratecode_cck(bitrate);
}
}
static u8 bcm43xx_calc_fallback_rate(u8 bitrate)
{
switch (bitrate) {
case IEEE80211_CCK_RATE_1MB:
return IEEE80211_CCK_RATE_1MB;
case IEEE80211_CCK_RATE_2MB:
return IEEE80211_CCK_RATE_1MB;
case IEEE80211_CCK_RATE_5MB:
return IEEE80211_CCK_RATE_2MB;
case IEEE80211_CCK_RATE_11MB:
return IEEE80211_CCK_RATE_5MB;
case IEEE80211_OFDM_RATE_6MB:
return IEEE80211_CCK_RATE_5MB;
case IEEE80211_OFDM_RATE_9MB:
return IEEE80211_OFDM_RATE_6MB;
case IEEE80211_OFDM_RATE_12MB:
return IEEE80211_OFDM_RATE_9MB;
case IEEE80211_OFDM_RATE_18MB:
return IEEE80211_OFDM_RATE_12MB;
case IEEE80211_OFDM_RATE_24MB:
return IEEE80211_OFDM_RATE_18MB;
case IEEE80211_OFDM_RATE_36MB:
return IEEE80211_OFDM_RATE_24MB;
case IEEE80211_OFDM_RATE_48MB:
return IEEE80211_OFDM_RATE_36MB;
case IEEE80211_OFDM_RATE_54MB:
return IEEE80211_OFDM_RATE_48MB;
}
assert(0);
return 0;
}
static
__le16 bcm43xx_calc_duration_id(const struct ieee80211_hdr *wireless_header,
u8 bitrate)
{
const u16 frame_ctl = le16_to_cpu(wireless_header->frame_ctl);
__le16 duration_id = wireless_header->duration_id;
switch (WLAN_FC_GET_TYPE(frame_ctl)) {
case IEEE80211_FTYPE_DATA:
case IEEE80211_FTYPE_MGMT:
//TODO: Steal the code from ieee80211, once it is completed there.
break;
case IEEE80211_FTYPE_CTL:
/* Use the original duration/id. */
break;
default:
assert(0);
}
return duration_id;
}
static inline
u16 ceiling_div(u16 dividend, u16 divisor)
{
return ((dividend + divisor - 1) / divisor);
}
static void bcm43xx_generate_rts(const struct bcm43xx_phyinfo *phy,
struct bcm43xx_txhdr *txhdr,
u16 *flags,
u8 bitrate,
const struct ieee80211_hdr_4addr *wlhdr)
{
u16 fctl;
u16 dur;
u8 fallback_bitrate;
int ofdm_modulation;
int fallback_ofdm_modulation;
// u8 *sa, *da;
u16 flen;
//FIXME sa = ieee80211_get_SA((struct ieee80211_hdr *)wlhdr);
//FIXME da = ieee80211_get_DA((struct ieee80211_hdr *)wlhdr);
fallback_bitrate = bcm43xx_calc_fallback_rate(bitrate);
ofdm_modulation = !(ieee80211_is_cck_rate(bitrate));
fallback_ofdm_modulation = !(ieee80211_is_cck_rate(fallback_bitrate));
flen = sizeof(u16) + sizeof(u16) + ETH_ALEN + ETH_ALEN + IEEE80211_FCS_LEN,
bcm43xx_generate_plcp_hdr((struct bcm43xx_plcp_hdr4 *)(&txhdr->rts_cts_plcp),
flen, bitrate,
!ieee80211_is_cck_rate(bitrate));
bcm43xx_generate_plcp_hdr((struct bcm43xx_plcp_hdr4 *)(&txhdr->rts_cts_fallback_plcp),
flen, fallback_bitrate,
!ieee80211_is_cck_rate(fallback_bitrate));
fctl = IEEE80211_FTYPE_CTL;
fctl |= IEEE80211_STYPE_RTS;
dur = le16_to_cpu(wlhdr->duration_id);
/*FIXME: should we test for dur==0 here and let it unmodified in this case?
* The following assert checks for this case...
*/
assert(dur);
/*FIXME: The duration calculation is not really correct.
* I am not 100% sure which bitrate to use. We use the RTS rate here,
* but this is likely to be wrong.
*/
if (phy->type == BCM43xx_PHYTYPE_A) {
/* Three times SIFS */
dur += 16 * 3;
/* Add ACK duration. */
dur += ceiling_div((16 + 8 * (14 /*bytes*/) + 6) * 10,
bitrate * 4);
/* Add CTS duration. */
dur += ceiling_div((16 + 8 * (14 /*bytes*/) + 6) * 10,
bitrate * 4);
} else {
/* Three times SIFS */
dur += 10 * 3;
/* Add ACK duration. */
dur += ceiling_div(8 * (14 /*bytes*/) * 10,
bitrate);
/* Add CTS duration. */
dur += ceiling_div(8 * (14 /*bytes*/) * 10,
bitrate);
}
txhdr->rts_cts_frame_control = cpu_to_le16(fctl);
txhdr->rts_cts_dur = cpu_to_le16(dur);
//printk(BCM43xx_MACFMT " " BCM43xx_MACFMT " " BCM43xx_MACFMT "\n", BCM43xx_MACARG(wlhdr->addr1), BCM43xx_MACARG(wlhdr->addr2), BCM43xx_MACARG(wlhdr->addr3));
//printk(BCM43xx_MACFMT " " BCM43xx_MACFMT "\n", BCM43xx_MACARG(sa), BCM43xx_MACARG(da));
memcpy(txhdr->rts_cts_mac1, wlhdr->addr1, ETH_ALEN);//FIXME!
// memcpy(txhdr->rts_cts_mac2, sa, ETH_ALEN);
*flags |= BCM43xx_TXHDRFLAG_RTSCTS;
*flags |= BCM43xx_TXHDRFLAG_RTS;
if (ofdm_modulation)
*flags |= BCM43xx_TXHDRFLAG_RTSCTS_OFDM;
if (fallback_ofdm_modulation)
*flags |= BCM43xx_TXHDRFLAG_RTSCTSFALLBACK_OFDM;
}
void bcm43xx_generate_txhdr(struct bcm43xx_private *bcm,
struct bcm43xx_txhdr *txhdr,
const unsigned char *fragment_data,
const unsigned int fragment_len,
const int is_first_fragment,
const u16 cookie)
{
const struct bcm43xx_phyinfo *phy = bcm43xx_current_phy(bcm);
const struct ieee80211_hdr_4addr *wireless_header = (const struct ieee80211_hdr_4addr *)fragment_data;
const struct ieee80211_security *secinfo = &bcm->ieee->sec;
u8 bitrate;
u8 fallback_bitrate;
int ofdm_modulation;
int fallback_ofdm_modulation;
u16 plcp_fragment_len = fragment_len;
u16 flags = 0;
u16 control = 0;
u16 wsec_rate = 0;
u16 encrypt_frame;
const u16 ftype = WLAN_FC_GET_TYPE(le16_to_cpu(wireless_header->frame_ctl));
const int is_mgt = (ftype == IEEE80211_FTYPE_MGMT);
/* Now construct the TX header. */
memset(txhdr, 0, sizeof(*txhdr));
bitrate = ieee80211softmac_suggest_txrate(bcm->softmac,
is_multicast_ether_addr(wireless_header->addr1), is_mgt);
ofdm_modulation = !(ieee80211_is_cck_rate(bitrate));
fallback_bitrate = bcm43xx_calc_fallback_rate(bitrate);
fallback_ofdm_modulation = !(ieee80211_is_cck_rate(fallback_bitrate));
/* Set Frame Control from 80211 header. */
txhdr->frame_control = wireless_header->frame_ctl;
/* Copy address1 from 80211 header. */
memcpy(txhdr->mac1, wireless_header->addr1, 6);
/* Set the fallback duration ID. */
txhdr->fallback_dur_id = bcm43xx_calc_duration_id((const struct ieee80211_hdr *)wireless_header,
fallback_bitrate);
/* Set the cookie (used as driver internal ID for the frame) */
txhdr->cookie = cpu_to_le16(cookie);
/* Hardware appends FCS. */
plcp_fragment_len += IEEE80211_FCS_LEN;
/* Hardware encryption. */
encrypt_frame = le16_to_cpup(&wireless_header->frame_ctl) & IEEE80211_FCTL_PROTECTED;
if (encrypt_frame && !bcm->ieee->host_encrypt) {
const struct ieee80211_hdr_3addr *hdr = (struct ieee80211_hdr_3addr *)wireless_header;
memcpy(txhdr->wep_iv, hdr->payload, 4);
/* Hardware appends ICV. */
plcp_fragment_len += 4;
wsec_rate |= (bcm->key[secinfo->active_key].algorithm << BCM43xx_TXHDR_WSEC_ALGO_SHIFT)
& BCM43xx_TXHDR_WSEC_ALGO_MASK;
wsec_rate |= (secinfo->active_key << BCM43xx_TXHDR_WSEC_KEYINDEX_SHIFT)
& BCM43xx_TXHDR_WSEC_KEYINDEX_MASK;
}
/* Generate the PLCP header and the fallback PLCP header. */
bcm43xx_generate_plcp_hdr((struct bcm43xx_plcp_hdr4 *)(&txhdr->plcp),
plcp_fragment_len,
bitrate, ofdm_modulation);
bcm43xx_generate_plcp_hdr(&txhdr->fallback_plcp, plcp_fragment_len,
fallback_bitrate, fallback_ofdm_modulation);
/* Set the CONTROL field */
if (ofdm_modulation)
control |= BCM43xx_TXHDRCTL_OFDM;
if (bcm->short_preamble) //FIXME: could be the other way around, please test
control |= BCM43xx_TXHDRCTL_SHORT_PREAMBLE;
control |= (phy->antenna_diversity << BCM43xx_TXHDRCTL_ANTENNADIV_SHIFT)
& BCM43xx_TXHDRCTL_ANTENNADIV_MASK;
/* Set the FLAGS field */
if (!is_multicast_ether_addr(wireless_header->addr1) &&
!is_broadcast_ether_addr(wireless_header->addr1))
flags |= BCM43xx_TXHDRFLAG_EXPECTACK;
if (1 /* FIXME: PS poll?? */)
flags |= 0x10; // FIXME: unknown meaning.
if (fallback_ofdm_modulation)
flags |= BCM43xx_TXHDRFLAG_FALLBACKOFDM;
if (is_first_fragment)
flags |= BCM43xx_TXHDRFLAG_FIRSTFRAGMENT;
/* Set WSEC/RATE field */
wsec_rate |= (txhdr->plcp.raw[0] << BCM43xx_TXHDR_RATE_SHIFT)
& BCM43xx_TXHDR_RATE_MASK;
/* Generate the RTS/CTS packet, if required. */
/* FIXME: We should first try with CTS-to-self,
* if we are on 80211g. If we get too many
* failures (hidden nodes), we should switch back to RTS/CTS.
*/
if (0/*FIXME txctl->use_rts_cts*/) {
bcm43xx_generate_rts(phy, txhdr, &flags,
0/*FIXME txctl->rts_cts_rate*/,
wireless_header);
}
txhdr->flags = cpu_to_le16(flags);
txhdr->control = cpu_to_le16(control);
txhdr->wsec_rate = cpu_to_le16(wsec_rate);
}
static s8 bcm43xx_rssi_postprocess(struct bcm43xx_private *bcm,
u8 in_rssi, int ofdm,
int adjust_2053, int adjust_2050)
{
struct bcm43xx_radioinfo *radio = bcm43xx_current_radio(bcm);
struct bcm43xx_phyinfo *phy = bcm43xx_current_phy(bcm);
s32 tmp;
switch (radio->version) {
case 0x2050:
if (ofdm) {
tmp = in_rssi;
if (tmp > 127)
tmp -= 256;
tmp *= 73;
tmp /= 64;
if (adjust_2050)
tmp += 25;
else
tmp -= 3;
} else {
if (bcm->sprom.boardflags & BCM43xx_BFL_RSSI) {
if (in_rssi > 63)
in_rssi = 63;
tmp = radio->nrssi_lt[in_rssi];
tmp = 31 - tmp;
tmp *= -131;
tmp /= 128;
tmp -= 57;
} else {
tmp = in_rssi;
tmp = 31 - tmp;
tmp *= -149;
tmp /= 128;
tmp -= 68;
}
if (phy->type == BCM43xx_PHYTYPE_G &&
adjust_2050)
tmp += 25;
}
break;
case 0x2060:
if (in_rssi > 127)
tmp = in_rssi - 256;
else
tmp = in_rssi;
break;
default:
tmp = in_rssi;
tmp -= 11;
tmp *= 103;
tmp /= 64;
if (adjust_2053)
tmp -= 109;
else
tmp -= 83;
}
return (s8)tmp;
}
//TODO
#if 0
static s8 bcm43xx_rssinoise_postprocess(struct bcm43xx_private *bcm,
u8 in_rssi)
{
struct bcm43xx_phyinfo *phy = bcm43xx_current_phy(bcm);
s8 ret;
if (phy->type == BCM43xx_PHYTYPE_A) {
//TODO: Incomplete specs.
ret = 0;
} else
ret = bcm43xx_rssi_postprocess(bcm, in_rssi, 0, 1, 1);
return ret;
}
#endif
int bcm43xx_rx(struct bcm43xx_private *bcm,
struct sk_buff *skb,
struct bcm43xx_rxhdr *rxhdr)
{
struct bcm43xx_radioinfo *radio = bcm43xx_current_radio(bcm);
struct bcm43xx_phyinfo *phy = bcm43xx_current_phy(bcm);
struct bcm43xx_plcp_hdr4 *plcp;
struct ieee80211_rx_stats stats;
struct ieee80211_hdr_4addr *wlhdr;
u16 frame_ctl;
int is_packet_for_us = 0;
int err = -EINVAL;
const u16 rxflags1 = le16_to_cpu(rxhdr->flags1);
const u16 rxflags2 = le16_to_cpu(rxhdr->flags2);
const u16 rxflags3 = le16_to_cpu(rxhdr->flags3);
const int is_ofdm = !!(rxflags1 & BCM43xx_RXHDR_FLAGS1_OFDM);
if (rxflags2 & BCM43xx_RXHDR_FLAGS2_TYPE2FRAME) {
plcp = (struct bcm43xx_plcp_hdr4 *)(skb->data + 2);
/* Skip two unknown bytes and the PLCP header. */
skb_pull(skb, 2 + sizeof(struct bcm43xx_plcp_hdr6));
} else {
plcp = (struct bcm43xx_plcp_hdr4 *)(skb->data);
/* Skip the PLCP header. */
skb_pull(skb, sizeof(struct bcm43xx_plcp_hdr6));
}
/* The SKB contains the PAYLOAD (wireless header + data)
* at this point. The FCS at the end is stripped.
*/
memset(&stats, 0, sizeof(stats));
stats.mac_time = le16_to_cpu(rxhdr->mactime);
stats.rssi = rxhdr->rssi;
stats.signal = bcm43xx_rssi_postprocess(bcm, rxhdr->rssi, is_ofdm,
!!(rxflags1 & BCM43xx_RXHDR_FLAGS1_2053RSSIADJ),
!!(rxflags3 & BCM43xx_RXHDR_FLAGS3_2050RSSIADJ));
stats.noise = bcm->stats.noise;
if (is_ofdm)
stats.rate = bcm43xx_plcp_get_bitrate_ofdm(plcp);
else
stats.rate = bcm43xx_plcp_get_bitrate_cck(plcp);
stats.received_channel = radio->channel;
stats.mask = IEEE80211_STATMASK_SIGNAL |
IEEE80211_STATMASK_NOISE |
IEEE80211_STATMASK_RATE |
IEEE80211_STATMASK_RSSI;
if (phy->type == BCM43xx_PHYTYPE_A)
stats.freq = IEEE80211_52GHZ_BAND;
else
stats.freq = IEEE80211_24GHZ_BAND;
stats.len = skb->len;
bcm->stats.last_rx = jiffies;
if (bcm->ieee->iw_mode == IW_MODE_MONITOR) {
err = ieee80211_rx(bcm->ieee, skb, &stats);
return (err == 0) ? -EINVAL : 0;
}
wlhdr = (struct ieee80211_hdr_4addr *)(skb->data);
switch (bcm->ieee->iw_mode) {
case IW_MODE_ADHOC:
if (memcmp(wlhdr->addr1, bcm->net_dev->dev_addr, ETH_ALEN) == 0 ||
memcmp(wlhdr->addr3, bcm->ieee->bssid, ETH_ALEN) == 0 ||
is_broadcast_ether_addr(wlhdr->addr1) ||
is_multicast_ether_addr(wlhdr->addr1) ||
bcm->net_dev->flags & IFF_PROMISC)
is_packet_for_us = 1;
break;
case IW_MODE_INFRA:
default:
/* When receiving multicast or broadcast packets, filter out
the packets we send ourself; we shouldn't see those */
if (memcmp(wlhdr->addr3, bcm->ieee->bssid, ETH_ALEN) == 0 ||
memcmp(wlhdr->addr1, bcm->net_dev->dev_addr, ETH_ALEN) == 0 ||
(memcmp(wlhdr->addr3, bcm->net_dev->dev_addr, ETH_ALEN) &&
(is_broadcast_ether_addr(wlhdr->addr1) ||
is_multicast_ether_addr(wlhdr->addr1) ||
bcm->net_dev->flags & IFF_PROMISC)))
is_packet_for_us = 1;
break;
}
frame_ctl = le16_to_cpu(wlhdr->frame_ctl);
switch (WLAN_FC_GET_TYPE(frame_ctl)) {
case IEEE80211_FTYPE_MGMT:
ieee80211_rx_mgt(bcm->ieee, wlhdr, &stats);
break;
case IEEE80211_FTYPE_DATA:
if (is_packet_for_us) {
err = ieee80211_rx(bcm->ieee, skb, &stats);
err = (err == 0) ? -EINVAL : 0;
}
break;
case IEEE80211_FTYPE_CTL:
break;
default:
assert(0);
return -EINVAL;
}
return err;
}