/*
 * ---------------------------------------------------------------------------
 *  FILE:     monitor.c
 *
 * Copyright (C) 2006-2008 by Cambridge Silicon Radio Ltd.
 *
 * Refer to LICENSE.txt included with this source code for details on
 * the license terms.
 *
 * ---------------------------------------------------------------------------
 */

#include "unifi_priv.h"

#ifdef UNIFI_SNIFF_ARPHRD


#if (UNIFI_SNIFF_ARPHRD == ARPHRD_IEEE80211_RADIOTAP)
#include <net/ieee80211_radiotap.h>
#endif

#ifndef ETH_P_80211_RAW
#define ETH_P_80211_RAW ETH_P_ALL
#endif

/*
 * ---------------------------------------------------------------------------
 *  uf_start_sniff
 *
 *      Start UniFi capture in SNIFF mode, i.e capture everything it hears.
 *
 *  Arguments:
 *      priv            Pointer to device private context struct
 *
 *  Returns:
 *      0 on success or kernel error code
 * ---------------------------------------------------------------------------
 */
int
uf_start_sniff(unifi_priv_t *priv)
{
    ul_client_t *pcli = priv->wext_client;
    CSR_SIGNAL signal;
    CSR_MLME_SNIFFJOIN_REQUEST *req = &signal.u.MlmeSniffjoinRequest;
    int timeout = 1000;
    int r;

    req->Ifindex = priv->if_index;
    req->Channel = priv->wext_conf.channel;
    req->ChannelStartingFactor = 0;

    signal.SignalPrimitiveHeader.SignalId = CSR_MLME_SNIFFJOIN_REQUEST_ID;

    r = unifi_mlme_blocking_request(priv, pcli, &signal, NULL, timeout);
    if (r < 0) {
        unifi_error(priv, "failed to send SNIFFJOIN request, error %d\n", r);
        return r;
    }

    r = pcli->reply_signal->u.MlmeSniffjoinConfirm.Resultcode;
    if (r) {
        unifi_notice(priv, "SNIFFJOIN request was rejected with result 0x%X (%s)\n",
                     r, lookup_result_code(r));
        return -EIO;
    }

    return 0;
} /* uf_start_sniff() */



/*
 * ---------------------------------------------------------------------------
 * netrx_radiotap
 *
 *      Reformat a UniFi SNIFFDATA signal into a radiotap packet.
 *
 * Arguments:
 *      priv            OS private context pointer.
 *      ind             Pointer to a MA_UNITDATA_INDICATION or
 *                      DS_UNITDATA_INDICATION indication structure.
 *
 * Notes:
 *      Radiotap header values are all little-endian, UniFi signals will have
 *      been converted to host-endian.
 * ---------------------------------------------------------------------------
 */
#if (UNIFI_SNIFF_ARPHRD == ARPHRD_IEEE80211_RADIOTAP)
static void
netrx_radiotap(unifi_priv_t *priv,
               const CSR_MA_SNIFFDATA_INDICATION *ind,
               struct sk_buff *skb_orig)
{
    struct net_device *dev = priv->netdev;
    struct sk_buff *skb = NULL;
    unsigned char *ptr;
    unsigned char *base;
    int ind_data_len = skb_orig->len - 2 - ETH_HLEN;
    struct unifi_rx_radiotap_header {
        struct ieee80211_radiotap_header rt_hdr;
        /* IEEE80211_RADIOTAP_TSFT */
        u64 rt_tsft;
        /* IEEE80211_RADIOTAP_FLAGS */
        u8  rt_flags;
        /* IEEE80211_RADIOTAP_RATE */
        u8  rt_rate;
        /* IEEE80211_RADIOTAP_CHANNEL */
        u16 rt_chan;
        u16 rt_chan_flags;
        /* IEEE80211_RADIOTAP_DBM_ANTSIGNAL */
        u8  rt_dbm_antsignal;
        /* IEEE80211_RADIOTAP_DBM_ANTNOISE */
        u8  rt_dbm_antnoise;
        /* IEEE80211_RADIOTAP_ANTENNA */
        u8  rt_antenna;

        /* pad to 4-byte boundary */
        u8 pad[3];
    } __attribute__((__packed__));

    struct unifi_rx_radiotap_header *unifi_rt;
    int signal, noise, snr;

    if (ind_data_len <= 0) {
        unifi_error(priv, "Invalid length in CSR_MA_SNIFFDATA_INDICATION.\n");
        return;
    }

    /*
     * Allocate a SKB for the received data packet, including radiotap
     * header.
     */
    skb = dev_alloc_skb(ind_data_len + sizeof(struct unifi_rx_radiotap_header) + 4);
    if (! skb) {
        unifi_error(priv, "alloc_skb failed.\n");
        priv->stats.rx_errors++;
        return;
    }

    base = skb->data;

    /* Reserve the radiotap header at the front of skb */
    unifi_rt = (struct unifi_rx_radiotap_header *)
        skb_put(skb, sizeof(struct unifi_rx_radiotap_header));

    /* Copy in the 802.11 frame */
    ptr = skb_put(skb, ind_data_len);
    memcpy(ptr, skb_orig->data, ind_data_len);

    unifi_rt->rt_hdr.it_version = PKTHDR_RADIOTAP_VERSION;
    unifi_rt->rt_hdr.it_pad = 0;	/* always good to zero */
    unifi_rt->rt_hdr.it_len = sizeof(struct unifi_rx_radiotap_header);

    /* Big bitfield of all the fields we provide in radiotap */
    unifi_rt->rt_hdr.it_present = 0
        | (1 << IEEE80211_RADIOTAP_TSFT)
        | (1 << IEEE80211_RADIOTAP_FLAGS)
        | (1 << IEEE80211_RADIOTAP_RATE)
        | (1 << IEEE80211_RADIOTAP_CHANNEL)
        | (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL)
        | (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE)
        | (1 << IEEE80211_RADIOTAP_ANTENNA)
        ;


    /* No flags to set */
    unifi_rt->rt_tsft = (((u64)ind->Timestamp.x[7]) | (((u64)ind->Timestamp.x[6]) << 8) |
                         (((u64)ind->Timestamp.x[5]) << 16) | (((u64)ind->Timestamp.x[4]) << 24) |
                         (((u64)ind->Timestamp.x[3]) << 32) | (((u64)ind->Timestamp.x[2]) << 40) |
                         (((u64)ind->Timestamp.x[1]) << 48) | (((u64)ind->Timestamp.x[0]) << 56));

    unifi_rt->rt_flags = 0;

    unifi_rt->rt_rate = ind->Rate;

    unifi_rt->rt_chan = cpu_to_le16(ieee80211chan2mhz(priv->wext_conf.channel));
    unifi_rt->rt_chan_flags = 0;

    /* Convert signal to dBm */
    signal = (s16)unifi2host_16(ind->Rssi);  /* in dBm */
    snr    = (s16)unifi2host_16(ind->Snr);   /* in dB */
    noise  = signal - snr;

    unifi_rt->rt_dbm_antsignal = signal;
    unifi_rt->rt_dbm_antnoise = noise;

    unifi_rt->rt_antenna = ind->AntennaId;


    skb->dev = dev;
    skb->mac_header = skb->data;
    skb->pkt_type = PACKET_OTHERHOST;
    skb->protocol = __constant_htons(ETH_P_80211_RAW);
    memset(skb->cb, 0, sizeof(skb->cb));

    /* Pass up to Linux network stack */
    netif_rx_ni(skb);

    dev->last_rx = jiffies;

    /* Bump the rx stats */
    priv->stats.rx_packets++;
    priv->stats.rx_bytes += ind_data_len;

} /* netrx_radiotap() */
#endif /* RADIOTAP */


/*
 * ---------------------------------------------------------------------------
 * netrx_prism
 *
 *      Reformat a UniFi SNIFFDATA signal into a Prism format sniff packet.
 *
 * Arguments:
 *      priv            OS private context pointer.
 *      ind             Pointer to a MA_UNITDATA_INDICATION or
 *                      DS_UNITDATA_INDICATION indication structure.
 *
 * Notes:
 *      Radiotap header values are all little-endian, UniFi signals will have
 *      been converted to host-endian.
 * ---------------------------------------------------------------------------
 */
#if (UNIFI_SNIFF_ARPHRD == ARPHRD_IEEE80211_PRISM)
static void
netrx_prism(unifi_priv_t *priv,
            const CSR_MA_SNIFFDATA_INDICATION *ind,
            struct sk_buff *skb_orig)
{
    struct net_device *dev = priv->netdev;
    struct sk_buff *skb = NULL;
    unsigned char *ptr;
    unsigned char *base;
    int ind_data_len = skb_orig->len - 2 - ETH_HLEN;
#define WLANCAP_MAGIC_COOKIE_V1 0x80211001
    struct avs_header_v1 {
        uint32  version;
        uint32  length;
        uint64  mactime;
        uint64  hosttime;
        uint32  phytype;
        uint32  channel;
        uint32  datarate;
        uint32  antenna;
        uint32  priority;
        uint32  ssi_type;
        int32   ssi_signal;
        int32   ssi_noise;
        uint32  preamble;
        uint32  encoding;
    } *avs;
    int signal, noise, snr;

    if (ind_data_len <= 0) {
        unifi_error(priv, "Invalid length in CSR_MA_SNIFFDATA_INDICATION.\n");
        return;
    }

    /*
     * Allocate a SKB for the received data packet, including radiotap
     * header.
     */
    skb = dev_alloc_skb(ind_data_len + sizeof(struct avs_header_v1) + 4);
    if (! skb) {
        unifi_error(priv, "alloc_skb failed.\n");
        priv->stats.rx_errors++;
        return;
    }

    base = skb->data;

    /* Reserve the radiotap header at the front of skb */
    avs = (struct avs_header_v1 *)skb_put(skb, sizeof(struct avs_header_v1));

    /* Copy in the 802.11 frame */
    ptr = skb_put(skb, ind_data_len);
    memcpy(ptr, skb_orig->data, ind_data_len);

    /* Convert signal to dBm */
    signal = 0x10000 - ((s16)unifi2host_16(ind->Rssi));  /* in dBm */
    snr    = (s16)unifi2host_16(ind->Snr);   /* in dB */
    noise  = signal - snr;

    avs->version        = htonl(WLANCAP_MAGIC_COOKIE_V1);
    avs->length         = htonl(sizeof(struct avs_header_v1));
    avs->mactime        = __cpu_to_be64(ind->Timestamp);
    avs->hosttime       = __cpu_to_be64(jiffies);
    avs->phytype        = htonl(9);             /* dss_ofdm_dot11_g */
    avs->channel        = htonl(priv->wext_conf.channel);
    avs->datarate       = htonl(ind->Rate * 5);
    avs->antenna        = htonl(ind->Antenna);
    avs->priority       = htonl(0);             /* unknown */
    avs->ssi_type       = htonl(2);             /* dBm */
    avs->ssi_signal     = htonl(signal);
    avs->ssi_noise      = htonl(noise);
    avs->preamble       = htonl(0); /* unknown */
    avs->encoding       = htonl(0); /* unknown */


    skb->dev = dev;
    skb->mac.raw = skb->data;
    skb->pkt_type = PACKET_OTHERHOST;
    skb->protocol = __constant_htons(ETH_P_80211_RAW);
    memset(skb->cb, 0, sizeof(skb->cb));

    /* Pass up to Linux network stack */
    netif_rx_ni(skb);

    dev->last_rx = jiffies;

    /* Bump the rx stats */
    priv->stats.rx_packets++;
    priv->stats.rx_bytes += ind_data_len;

} /* netrx_prism() */
#endif /* PRISM */


/*
 * ---------------------------------------------------------------------------
 * ma_sniffdata_ind
 *
 *      Reformat a UniFi SNIFFDATA signal into a network
 *
 * Arguments:
 *      ospriv          OS private context pointer.
 *      ind             Pointer to a MA_UNITDATA_INDICATION or
 *                      DS_UNITDATA_INDICATION indication structure.
 *      bulkdata        Pointer to a bulk data structure, describing
 *                      the data received.
 *
 * Notes:
 *      Radiotap header values are all little-endian, UniFi signals will have
 *      been converted to host-endian.
 * ---------------------------------------------------------------------------
 */
void
ma_sniffdata_ind(void *ospriv,
                 const CSR_MA_SNIFFDATA_INDICATION *ind,
                 const bulk_data_param_t *bulkdata)
{
    unifi_priv_t *priv = ospriv;
    struct net_device *dev = priv->netdev;
    struct sk_buff *skb = (struct sk_buff*)bulkdata->d[0].os_net_buf_ptr;

    if (bulkdata->d[0].data_length == 0) {
        unifi_warning(priv, "rx: MA-SNIFFDATA indication with zero bulk data\n");
        return;
    }

    skb->len = bulkdata->d[0].data_length;

    /* We only process data packets if the interface is open */
    if (unlikely(!netif_running(dev))) {
        priv->stats.rx_dropped++;
        priv->wext_conf.wireless_stats.discard.misc++;
        dev_kfree_skb(skb);
        return;
    }

    if (ind->ReceptionStatus) {
        priv->stats.rx_dropped++;
        priv->wext_conf.wireless_stats.discard.misc++;
        printk(KERN_INFO "unifi: Dropping corrupt sniff packet\n");
        dev_kfree_skb(skb);
        return;
    }

#if (UNIFI_SNIFF_ARPHRD == ARPHRD_IEEE80211_PRISM)
    netrx_prism(priv, ind, skb);
#endif /* PRISM */

#if (UNIFI_SNIFF_ARPHRD == ARPHRD_IEEE80211_RADIOTAP)
    netrx_radiotap(priv, ind, skb);
#endif /* RADIOTAP */

    dev_kfree_skb(skb);

} /* ma_sniffdata_ind() */


#endif /* UNIFI_SNIFF_ARPHRD */