Skip to contentSkip to conversation

ath5k descriptor cleanup + ibss beacon fix

PATCH New Bruno Randolf 1 participant 6 messages Mar 5, 2008 Networking drivers (wireless)

lore
Share & tools
lore
#0 lore raw

cover ath5k descriptor cleanup + ibss beacon fix

hello!

here are some patches, some i have had lying around for a while.

especially the first two touch the very core of the driver, so please review them. i have been careful and i don't think it creates a problem, but i have tested it only with ar5212 because i don't have a 5210 to test with unfortunately.

the other ones are just about rx timestamps and a workaround for some hardware bugs in ibss mode.

Bruno Randolf (4):
ath5k: work around wrong beacon rx timestamp in IBSS mode
ath5k: add notes about rx timestamp
ath5k: move rx and tx status structures out of hardware descriptor
ath5k: struct ath5k_desc cleanups

 drivers/net/wireless/ath5k/ath5k.h |   35 ++--
 drivers/net/wireless/ath5k/base.c  |  144 ++++++++++------
 drivers/net/wireless/ath5k/debug.c |   31 ++-
 drivers/net/wireless/ath5k/debug.h |    6 -
 drivers/net/wireless/ath5k/hw.c    |  331 ++++++++++++++++++------------------
 drivers/net/wireless/ath5k/hw.h    |  150 ++++++++++------
 6 files changed, 379 insertions(+), 318 deletions(-)
Signature · 1 line
Signature
#1 lore raw

PATCH 1/4 ath5k: struct ath5k_desc cleanups

From: Bruno Randolf <[email protected]>

* make struct ath5k_desc clearer by directly including unions of structures, which correspond to the hardware descriptors of different HW versions (5210 and 5212). before they were casted at onto ath5k_desc at different points (e.g. ds_hw[2]).

* rename some structures and variable names to make their use clearer, e.g. struct ath5k_hw_4w_tx_desc to ath5k_hw_4w_tx_ctl.

* substitute "old" with "5210" and "new" with "5212" (eg. rename ath5k_hw_proc_new_rx_status() to ath5k_hw_proc_5212_rx_status()) because old and new are relative and we might have a newer structure at some point.

* unify structs ath5k_hw_old_rx_status and ath5k_hw_new_rx_status into one ath5k_hw_rx_status, because they only differ in the flags and masks.

drivers/net/wireless/ath5k/ath5k.h:     Changes-licensed-under: ISC
drivers/net/wireless/ath5k/debug.c:     Changes-licensed-under: GPL
drivers/net/wireless/ath5k/hw.c:        Changes-licensed-under: ISC
drivers/net/wireless/ath5k/hw.h:        Changes-licensed-under: ISC

---

 drivers/net/wireless/ath5k/ath5k.h |   23 ++--
 drivers/net/wireless/ath5k/debug.c |   12 +-
 drivers/net/wireless/ath5k/hw.c    |  212 +++++++++++++++++-------------------
 drivers/net/wireless/ath5k/hw.h    |  150 +++++++++++++++----------
 4 files changed, 210 insertions(+), 187 deletions(-)
drivers/net/wireless/ath5k/ath5k.h +12 -11
index 18223d9..e2f889b 100644
@@ -268,12 +268,13 @@ enum ath5k_driver_mode {
 #define SHPREAMBLE_FLAG(_ix) \
 	(HAS_SHPREAMBLE(_ix) ? AR5K_SET_SHORT_PREAMBLE : 0)
 
+
 /****************\
   TX DEFINITIONS
 \****************/
 
 /*
- * Tx Descriptor
+ * TX Status
  */
 struct ath5k_tx_status {
 	u16	ts_seqnum;
@@ -421,7 +422,7 @@ enum ath5k_dmasize {
 \****************/
 
 /*
- * Rx Descriptor
+ * RX Status
  */
 struct ath5k_rx_status {
 	u16	rs_datalen;
@@ -452,8 +453,6 @@ struct ath5k_mib_stats {
 };
 
 
-
-
 /**************************\
  BEACON TIMERS DEFINITIONS
 \**************************/
@@ -495,20 +494,22 @@ struct ath5k_beacon_state {
 #define TSF_TO_TU(_tsf) (u32)((_tsf) >> 10)
 
 
-
 /********************\
   COMMON DEFINITIONS
 \********************/
 
 /*
- * Atheros descriptor
+ * Atheros hardware descriptor
  */
 struct ath5k_desc {
-	u32	ds_link;
-	u32	ds_data;
-	u32	ds_ctl0;
-	u32	ds_ctl1;
-	u32	ds_hw[4];
+	u32	ds_link;	/* physical address of the next descriptor */
+	u32	ds_data;	/* physical address of data buffer (skb) */
+
+	union {
+		struct ath5k_hw_5210_tx_desc	ds_tx5210;
+		struct ath5k_hw_5212_tx_desc	ds_tx5212;
+		struct ath5k_hw_all_rx_desc	ds_rx;
+	} ud;
 
 	union {
 		struct ath5k_rx_status rx;
drivers/net/wireless/ath5k/debug.c +8 -4
index 05bf4fb..9301494 100644
@@ -500,11 +500,13 @@ static inline void
 ath5k_debug_printrxbuf(struct ath5k_buf *bf, int done)
 {
 	struct ath5k_desc *ds = bf->desc;
+	struct ath5k_hw_all_rx_desc *rd = &ds->ud.ds_rx;
 
 	printk(KERN_DEBUG "R (%p %llx) %08x %08x %08x %08x %08x %08x %c\n",
 		ds, (unsigned long long)bf->daddr,
-		ds->ds_link, ds->ds_data, ds->ds_ctl0, ds->ds_ctl1,
-		ds->ds_hw[0], ds->ds_hw[1],
+		ds->ds_link, ds->ds_data,
+		rd->rx_ctl.rx_control_0, rd->rx_ctl.rx_control_1,
+		rd->u.rx_stat.rx_status_0, rd->u.rx_stat.rx_status_0,
 		!done ? ' ' : (ds->ds_rxstat.rs_status == 0) ? '*' : '!');
 }
 
@@ -554,14 +556,16 @@ ath5k_debug_printtxbuf(struct ath5k_softc *sc,
 			struct ath5k_buf *bf, int done)
 {
 	struct ath5k_desc *ds = bf->desc;
+	struct ath5k_hw_5212_tx_desc *td = &ds->ud.ds_tx5212;
 
 	if (likely(!(sc->debug.level & ATH5K_DEBUG_RESET)))
 		return;
 
 	printk(KERN_DEBUG "T (%p %llx) %08x %08x %08x %08x %08x %08x %08x "
 		"%08x %c\n", ds, (unsigned long long)bf->daddr, ds->ds_link,
-		ds->ds_data, ds->ds_ctl0, ds->ds_ctl1,
-		ds->ds_hw[0], ds->ds_hw[1], ds->ds_hw[2], ds->ds_hw[3],
+		ds->ds_data, td->tx_ctl.tx_control_0, td->tx_ctl.tx_control_1,
+		td->tx_ctl.tx_control_2, td->tx_ctl.tx_control_3,
+		td->tx_stat.tx_status_0, td->tx_stat.tx_status_1,
 		!done ? ' ' : (ds->ds_txstat.ts_status == 0) ? '*' : '!');
 }
 
drivers/net/wireless/ath5k/hw.c +101 -111

506 lines not on this page · raw message

drivers/net/wireless/ath5k/hw.h +89 -61

194 lines not on this page · raw message

#2 lore raw

PATCH 1/1 ath5k: struct ath5k_desc cleanups

* make struct ath5k_desc clearer by directly including unions of structures, which correspond to the hardware descriptors of different HW versions (5210 and 5212). before they were casted at onto ath5k_desc at different points (e.g. ds_hw[2]).

* rename some structures and variable names to make their use clearer, e.g. struct ath5k_hw_4w_tx_desc to ath5k_hw_4w_tx_ctl.

* substitute "old" with "5210" and "new" with "5212" (eg. rename ath5k_hw_proc_new_rx_status() to ath5k_hw_proc_5212_rx_status()) because old and new are relative and we might have a newer structure at some point.

* unify structs ath5k_hw_old_rx_status and ath5k_hw_new_rx_status into one ath5k_hw_rx_status, because they only differ in the flags and masks.

drivers/net/wireless/ath5k/ath5k.h:     Changes-licensed-under: ISC
drivers/net/wireless/ath5k/debug.c:     Changes-licensed-under: GPL
drivers/net/wireless/ath5k/hw.c:        Changes-licensed-under: ISC
drivers/net/wireless/ath5k/hw.h:        Changes-licensed-under: ISC

---

 drivers/net/wireless/ath5k/ath5k.h |   23 ++--
 drivers/net/wireless/ath5k/debug.c |   12 +-
 drivers/net/wireless/ath5k/hw.c    |  212 +++++++++++++++++-------------------
 drivers/net/wireless/ath5k/hw.h    |  150 +++++++++++++++----------
 4 files changed, 210 insertions(+), 187 deletions(-)
drivers/net/wireless/ath5k/ath5k.h +12 -11
index 18223d9..e2f889b 100644
@@ -268,12 +268,13 @@ enum ath5k_driver_mode {
 #define SHPREAMBLE_FLAG(_ix) \
 	(HAS_SHPREAMBLE(_ix) ? AR5K_SET_SHORT_PREAMBLE : 0)
 
+
 /****************\
   TX DEFINITIONS
 \****************/
 
 /*
- * Tx Descriptor
+ * TX Status
  */
 struct ath5k_tx_status {
 	u16	ts_seqnum;
@@ -421,7 +422,7 @@ enum ath5k_dmasize {
 \****************/
 
 /*
- * Rx Descriptor
+ * RX Status
  */
 struct ath5k_rx_status {
 	u16	rs_datalen;
@@ -452,8 +453,6 @@ struct ath5k_mib_stats {
 };
 
 
-
-
 /**************************\
  BEACON TIMERS DEFINITIONS
 \**************************/
@@ -495,20 +494,22 @@ struct ath5k_beacon_state {
 #define TSF_TO_TU(_tsf) (u32)((_tsf) >> 10)
 
 
-
 /********************\
   COMMON DEFINITIONS
 \********************/
 
 /*
- * Atheros descriptor
+ * Atheros hardware descriptor
  */
 struct ath5k_desc {
-	u32	ds_link;
-	u32	ds_data;
-	u32	ds_ctl0;
-	u32	ds_ctl1;
-	u32	ds_hw[4];
+	u32	ds_link;	/* physical address of the next descriptor */
+	u32	ds_data;	/* physical address of data buffer (skb) */
+
+	union {
+		struct ath5k_hw_5210_tx_desc	ds_tx5210;
+		struct ath5k_hw_5212_tx_desc	ds_tx5212;
+		struct ath5k_hw_all_rx_desc	ds_rx;
+	} ud;
 
 	union {
 		struct ath5k_rx_status rx;
drivers/net/wireless/ath5k/debug.c +8 -4
index 05bf4fb..9301494 100644
@@ -500,11 +500,13 @@ static inline void
 ath5k_debug_printrxbuf(struct ath5k_buf *bf, int done)
 {
 	struct ath5k_desc *ds = bf->desc;
+	struct ath5k_hw_all_rx_desc *rd = &ds->ud.ds_rx;
 
 	printk(KERN_DEBUG "R (%p %llx) %08x %08x %08x %08x %08x %08x %c\n",
 		ds, (unsigned long long)bf->daddr,
-		ds->ds_link, ds->ds_data, ds->ds_ctl0, ds->ds_ctl1,
-		ds->ds_hw[0], ds->ds_hw[1],
+		ds->ds_link, ds->ds_data,
+		rd->rx_ctl.rx_control_0, rd->rx_ctl.rx_control_1,
+		rd->u.rx_stat.rx_status_0, rd->u.rx_stat.rx_status_0,
 		!done ? ' ' : (ds->ds_rxstat.rs_status == 0) ? '*' : '!');
 }
 
@@ -554,14 +556,16 @@ ath5k_debug_printtxbuf(struct ath5k_softc *sc,
 			struct ath5k_buf *bf, int done)
 {
 	struct ath5k_desc *ds = bf->desc;
+	struct ath5k_hw_5212_tx_desc *td = &ds->ud.ds_tx5212;
 
 	if (likely(!(sc->debug.level & ATH5K_DEBUG_RESET)))
 		return;
 
 	printk(KERN_DEBUG "T (%p %llx) %08x %08x %08x %08x %08x %08x %08x "
 		"%08x %c\n", ds, (unsigned long long)bf->daddr, ds->ds_link,
-		ds->ds_data, ds->ds_ctl0, ds->ds_ctl1,
-		ds->ds_hw[0], ds->ds_hw[1], ds->ds_hw[2], ds->ds_hw[3],
+		ds->ds_data, td->tx_ctl.tx_control_0, td->tx_ctl.tx_control_1,
+		td->tx_ctl.tx_control_2, td->tx_ctl.tx_control_3,
+		td->tx_stat.tx_status_0, td->tx_stat.tx_status_1,
 		!done ? ' ' : (ds->ds_txstat.ts_status == 0) ? '*' : '!');
 }
 
drivers/net/wireless/ath5k/hw.c +101 -111

506 lines not on this page · raw message

drivers/net/wireless/ath5k/hw.h +89 -61

194 lines not on this page · raw message

#3 lore raw

PATCH 2/4 ath5k: move rx and tx status structures out of hardware descriptor

In reply to Bruno Randolf

move ath5k_tx_status and ath5k_rx_status structures out of the hardware descriptor since they are not accessed by the hardware at all. they just contain converted information from the hardware descriptor. since they are only used in the rx and tx tasklets there is also no use to keep them for each descriptor.

drivers/net/wireless/ath5k/ath5k.h:     Changes-licensed-under: ISC
drivers/net/wireless/ath5k/base.c:      Changes-licensed-under: 3-Clause-BSD
drivers/net/wireless/ath5k/debug.c:     Changes-licensed-under: GPL
drivers/net/wireless/ath5k/debug.h:     Changes-licensed-under: GPL
drivers/net/wireless/ath5k/hw.c:        Changes-licensed-under: ISC

---

 drivers/net/wireless/ath5k/ath5k.h |   16 +---
 drivers/net/wireless/ath5k/base.c  |   74 +++++++++----------
 drivers/net/wireless/ath5k/debug.c |   19 +++--
 drivers/net/wireless/ath5k/debug.h |    6 +-
 drivers/net/wireless/ath5k/hw.c    |  141 ++++++++++++++++++------------------
 5 files changed, 126 insertions(+), 130 deletions(-)
drivers/net/wireless/ath5k/ath5k.h +5 -11
index e2f889b..09b9852 100644
@@ -500,6 +500,7 @@ struct ath5k_beacon_state {
 
 /*
  * Atheros hardware descriptor
+ * This is read and written to by the hardware
  */
 struct ath5k_desc {
 	u32	ds_link;	/* physical address of the next descriptor */
@@ -510,15 +511,6 @@ struct ath5k_desc {
 		struct ath5k_hw_5212_tx_desc	ds_tx5212;
 		struct ath5k_hw_all_rx_desc	ds_rx;
 	} ud;
-
-	union {
-		struct ath5k_rx_status rx;
-		struct ath5k_tx_status tx;
-	} ds_us;
-
-#define ds_rxstat ds_us.rx
-#define ds_txstat ds_us.tx
-
 } __packed;
 
 #define AR5K_RXDESC_INTREQ	0x0020
@@ -1037,8 +1029,10 @@ struct ath5k_hw {
 	int (*ah_setup_xtx_desc)(struct ath5k_hw *, struct ath5k_desc *,
 		unsigned int, unsigned int, unsigned int, unsigned int,
 		unsigned int, unsigned int);
-	int (*ah_proc_tx_desc)(struct ath5k_hw *, struct ath5k_desc *);
-	int (*ah_proc_rx_desc)(struct ath5k_hw *, struct ath5k_desc *);
+	int (*ah_proc_tx_desc)(struct ath5k_hw *, struct ath5k_desc *,
+		struct ath5k_tx_status *);
+	int (*ah_proc_rx_desc)(struct ath5k_hw *, struct ath5k_desc *,
+		struct ath5k_rx_status *);
 };
 
 /*
drivers/net/wireless/ath5k/base.c +35 -39

196 lines not on this page · raw message

drivers/net/wireless/ath5k/debug.c +12 -7
index 9301494..d3eb9e8 100644
@@ -497,7 +497,8 @@ ath5k_debug_dump_bands(struct ath5k_softc *sc)
 }
 
 static inline void
-ath5k_debug_printrxbuf(struct ath5k_buf *bf, int done)
+ath5k_debug_printrxbuf(struct ath5k_buf *bf, int done,
+		       struct ath5k_rx_status *rs)
 {
 	struct ath5k_desc *ds = bf->desc;
 	struct ath5k_hw_all_rx_desc *rd = &ds->ud.ds_rx;
@@ -507,7 +508,7 @@ ath5k_debug_printrxbuf(struct ath5k_buf *bf, int done)
 		ds->ds_link, ds->ds_data,
 		rd->rx_ctl.rx_control_0, rd->rx_ctl.rx_control_1,
 		rd->u.rx_stat.rx_status_0, rd->u.rx_stat.rx_status_0,
-		!done ? ' ' : (ds->ds_rxstat.rs_status == 0) ? '*' : '!');
+		!done ? ' ' : (rs->rs_status == 0) ? '*' : '!');
 }
 
 void
@@ -515,6 +516,7 @@ ath5k_debug_printrxbuffs(struct ath5k_softc *sc, struct ath5k_hw *ah)
 {
 	struct ath5k_desc *ds;
 	struct ath5k_buf *bf;
+	struct ath5k_rx_status rs = {};
 	int status;
 
 	if (likely(!(sc->debug.level & ATH5K_DEBUG_RESET)))
@@ -526,9 +528,9 @@ ath5k_debug_printrxbuffs(struct ath5k_softc *sc, struct ath5k_hw *ah)
 	spin_lock_bh(&sc->rxbuflock);
 	list_for_each_entry(bf, &sc->rxbuf, list) {
 		ds = bf->desc;
-		status = ah->ah_proc_rx_desc(ah, ds);
+		status = ah->ah_proc_rx_desc(ah, ds, &rs);
 		if (!status)
-			ath5k_debug_printrxbuf(bf, status == 0);
+			ath5k_debug_printrxbuf(bf, status == 0, &rs);
 	}
 	spin_unlock_bh(&sc->rxbuflock);
 }
@@ -552,21 +554,24 @@ ath5k_debug_dump_skb(struct ath5k_softc *sc,
 }
 
 void
-ath5k_debug_printtxbuf(struct ath5k_softc *sc,
-			struct ath5k_buf *bf, int done)
+ath5k_debug_printtxbuf(struct ath5k_softc *sc, struct ath5k_buf *bf)
 {
 	struct ath5k_desc *ds = bf->desc;
 	struct ath5k_hw_5212_tx_desc *td = &ds->ud.ds_tx5212;
+	struct ath5k_tx_status ts = {};
+	int done;
 
 	if (likely(!(sc->debug.level & ATH5K_DEBUG_RESET)))
 		return;
 
+	done = sc->ah->ah_proc_tx_desc(sc->ah, bf->desc, &ts);
+
 	printk(KERN_DEBUG "T (%p %llx) %08x %08x %08x %08x %08x %08x %08x "
 		"%08x %c\n", ds, (unsigned long long)bf->daddr, ds->ds_link,
 		ds->ds_data, td->tx_ctl.tx_control_0, td->tx_ctl.tx_control_1,
 		td->tx_ctl.tx_control_2, td->tx_ctl.tx_control_3,
 		td->tx_stat.tx_status_0, td->tx_stat.tx_status_1,
-		!done ? ' ' : (ds->ds_txstat.ts_status == 0) ? '*' : '!');
+		done ? ' ' : (ts.ts_status == 0) ? '*' : '!');
 }
 
 #endif /* ifdef CONFIG_ATH5K_DEBUG */
drivers/net/wireless/ath5k/debug.h +2 -4
index 8c0b5c5..2cf8d18 100644
@@ -160,8 +160,7 @@ ath5k_debug_dump_skb(struct ath5k_softc *sc,
 			struct sk_buff *skb, const char *prefix, int tx);
 
 void
-ath5k_debug_printtxbuf(struct ath5k_softc *sc,
-			struct ath5k_buf *bf, int done);
+ath5k_debug_printtxbuf(struct ath5k_softc *sc, struct ath5k_buf *bf);
 
 #else /* no debugging */
 
@@ -199,8 +198,7 @@ ath5k_debug_dump_skb(struct ath5k_softc *sc,
 			struct sk_buff *skb, const char *prefix, int tx) {}
 
 static inline void
-ath5k_debug_printtxbuf(struct ath5k_softc *sc,
-			struct ath5k_buf *bf, int done) {}
+ath5k_debug_printtxbuf(struct ath5k_softc *sc, struct ath5k_buf *bf) {}
 
 #endif /* ifdef CONFIG_ATH5K_DEBUG */
 
drivers/net/wireless/ath5k/hw.c +72 -69

303 lines not on this page · raw message

#4 lore raw

PATCH 3/4 ath5k: add notes about rx timestamp

In reply to Bruno Randolf

add comments about the fact that we don't know when exactly the atheros hardware takes the RX timestamp.

drivers/net/wireless/ath5k/base.c: Changes-licensed-under: 3-Clause-BSD

---

 drivers/net/wireless/ath5k/base.c |   11 +++++++++++
 1 files changed, 11 insertions(+), 0 deletions(-)
drivers/net/wireless/ath5k/base.c +11 -0
index 777bc4a..03e05fe 100644
@@ -1841,6 +1841,17 @@ accept:
 		 * 15bit only. that means TSF extension has to be done within
 		 * 32768usec (about 32ms). it might be necessary to move this to
 		 * the interrupt handler, like it is done in madwifi.
+		 *
+		 * Unfortunately we don't know when the hardware takes the rx
+		 * timestamp (beginning of phy frame, data frame, end of rx?).
+		 * The only thing we know is that it is hardware specific...
+		 * On AR5213 it seems the rx timestamp is at the end of the
+		 * frame, but i'm not sure.
+		 *
+		 * NOTE: mac80211 defines mactime at the beginning of the first
+		 * data symbol. Since we don't have any time references it's
+		 * impossible to comply to that. This affects IBSS merge only
+		 * right now, so it's not too bad...
 		 */
 		rxs.mactime = ath5k_extend_tsf(sc->ah, rs.rs_tstamp);
 		rxs.flag |= RX_FLAG_TSFT;
#5 lore raw

PATCH 4/4 ath5k: work around wrong beacon rx timestamp in IBSS mode

In reply to Bruno Randolf

atheros hardware has a problem with the rx timestamp of some IBSS beacons when they caused a TSF update (they have the same BSSID).

the rx timestamp is wrong especially if the beacon frames get bigger than 78 byte (at least on AR5213 and AR5414 hardware). in that case ath5k_extend_tsf() will assume a rs_tstamp overflow and give us a timestamp too far in the past which will cause mac80211 to merge IBSS on every beacon (which is not necessary since the BSSID already matches). but in this case we know that the HW must have synced to the beacons TSF and the rx timestamp must be later than that so we can adjust mactime accordingly.

also rename the function to ath5k_check_ibss_tsf() and change comments, since "hw merge" is better described as a TSF update.

drivers/net/wireless/ath5k/base.c: Changes-licensed-under: 3-Clause-BSD

---

 drivers/net/wireless/ath5k/base.c |   59 ++++++++++++++++++++++++++++---------
 1 files changed, 45 insertions(+), 14 deletions(-)
drivers/net/wireless/ath5k/base.c +45 -14
index 03e05fe..5139217 100644
@@ -1712,8 +1712,10 @@ ath5k_rx_decrypted(struct ath5k_softc *sc, struct ath5k_desc *ds,
 
 
 static void
-ath5k_check_ibss_hw_merge(struct ath5k_softc *sc, struct sk_buff *skb)
+ath5k_check_ibss_tsf(struct ath5k_softc *sc, struct sk_buff *skb,
+		     struct ieee80211_rx_status *rxs)
 {
+	u64 tsf, bc_tstamp;
 	u32 hw_tu;
 	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
 
@@ -1724,16 +1726,45 @@ ath5k_check_ibss_hw_merge(struct ath5k_softc *sc, struct sk_buff *skb)
 	    le16_to_cpu(mgmt->u.beacon.capab_info) & WLAN_CAPABILITY_IBSS &&
 	    memcmp(mgmt->bssid, sc->ah->ah_bssid, ETH_ALEN) == 0) {
 		/*
-		 * Received an IBSS beacon with the same BSSID. Hardware might
-		 * have updated the TSF, check if we need to update timers.
+		 * Received an IBSS beacon with the same BSSID. Hardware *must*
+		 * have updated the local TSF. We have to work around various
+		 * hardware bugs, though...
 		 */
-		hw_tu = TSF_TO_TU(ath5k_hw_get_tsf64(sc->ah));
-		if (hw_tu >= sc->nexttbtt) {
-			ath5k_beacon_update_timers(sc,
-				le64_to_cpu(mgmt->u.beacon.timestamp));
+		tsf = ath5k_hw_get_tsf64(sc->ah);
+		bc_tstamp = le64_to_cpu(mgmt->u.beacon.timestamp);
+		hw_tu = TSF_TO_TU(tsf);
+
+		ATH5K_DBG_UNLIMIT(sc, ATH5K_DEBUG_BEACON,
+			"beacon %llx mactime %llx (diff %lld) tsf now %llx\n",
+			bc_tstamp, rxs->mactime,
+			(rxs->mactime - bc_tstamp), tsf);
+
+		/*
+		 * Sometimes the HW will give us a wrong tstamp in the rx
+		 * status, causing the timestamp extension to go wrong.
+		 * (This seems to happen especially with beacon frames bigger
+		 * than 78 byte (incl. FCS))
+		 * But we know that the receive timestamp must be later than the
+		 * timestamp of the beacon since HW must have synced to that.
+		 *
+		 * NOTE: here we assume mactime to be after the frame was
+		 * received, not like mac80211 which defines it at the start.
+		 */
+		if (bc_tstamp > rxs->mactime) {
 			ATH5K_DBG_UNLIMIT(sc, ATH5K_DEBUG_BEACON,
-				"detected HW merge from received beacon\n");
+				"fixing mactime from %llx to %llx\n",
+				rxs->mactime, tsf);
+			rxs->mactime = tsf;
 		}
+
+		/*
+		 * Local TSF might have moved higher than our beacon timers,
+		 * in that case we have to update them to continue sending
+		 * beacons. This also takes care of synchronizing beacon sending
+		 * times with other stations.
+		 */
+		if (hw_tu >= sc->nexttbtt)
+			ath5k_beacon_update_timers(sc, bc_tstamp);
 	}
 }
 
@@ -1882,7 +1913,7 @@ accept:
 
 		/* check beacons in IBSS mode */
 		if (sc->opmode == IEEE80211_IF_TYPE_IBSS)
-			ath5k_check_ibss_hw_merge(sc, skb);
+			ath5k_check_ibss_tsf(sc, skb, &rxs);
 
 		__ieee80211_rx(sc->hw, skb, &rxs);
 		sc->led_rxrate = rs.rs_rate;
@@ -2115,7 +2146,7 @@ ath5k_beacon_send(struct ath5k_softc *sc)
  * beacon timer registers.
  *
  * This is called in a variety of situations, e.g. when a beacon is received,
- * when a HW merge has been detected, but also when an new IBSS is created or
+ * when a TSF update has been detected, but also when an new IBSS is created or
  * when we otherwise know we have to update the timers, but we keep it in this
  * function to have it all together in one place.
  */
@@ -2215,7 +2246,7 @@ ath5k_beacon_update_timers(struct ath5k_softc *sc, u64 bc_tsf)
  * another AP to associate with.
  *
  * In IBSS mode we use a self-linked tx descriptor if possible. We enable SWBA
- * interrupts to detect HW merges only.
+ * interrupts to detect TSF updates only.
  *
  * AP mode is missing.
  */
@@ -2235,7 +2266,7 @@ ath5k_beacon_config(struct ath5k_softc *sc)
 		 * hardware send the beacons automatically. We have to load it
 		 * only once here.
 		 * We use the SWBA interrupt only to keep track of the beacon
-		 * timers in order to detect HW merges (automatic TSF updates).
+		 * timers in order to detect automatic TSF updates.
 		 */
 		ath5k_beaconq_config(sc);
 
@@ -2448,8 +2479,8 @@ ath5k_intr(int irq, void *dev_id)
 				*
 				* In IBSS mode we use this interrupt just to
 				* keep track of the next TBTT (target beacon
-				* transmission time) in order to detect hardware
-				* merges (TSF updates).
+				* transmission time) in order to detect wether
+				* automatic TSF updates happened.
 				*/
 				if (sc->opmode == IEEE80211_IF_TYPE_IBSS) {
 					 /* XXX: only if VEOL suppported */