Kernel driver modules ath9k and ath9k_htc basically discard a control frame after it processes and won’t pass it to an upper layer. To get a control frame at the upper layer, each modules has to be changed to set a corresponding bit of the rx filter.

We can find codes below:

recv.c in ath9k:

u32 ath_calcrxfilter(struct ath_softc *sc)
{
    /* omitted */
    if ((sc->cur_chan->rxfilter & FIF_CONTROL) ||
        sc->sc_ah->dynack.enabled)
        rfilt |= ATH9K_RX_FILTER_CONTROL;
    /* omitted */
}

htc_drv_txrx.c in ath9k_htc:

u32 ath9k_htc_calcrxfilter(struct ath9k_htc_priv *priv)
{
    /* omitted */
    if (priv->rxfilter & FIF_CONTROL)
        rfilt |= ATH9K_RX_FILTER_CONTROL;
    /* omitted */
}

By pulling rfilt |= ATH9K_RX_FILTER_CONTROL out of if block, they will pass control frames to the upper layer

Reference

  1. [PATCHv2 07/10] ath9k: enable control frame reception