aboutsummaryrefslogtreecommitdiff
path: root/drivers/staging/csr/monitor.c
blob: c8e20e4c6111a93577250f54b59c11c437295322 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
/*
 * ---------------------------------------------------------------------------
 *  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 */