blob: 84835dd305bc8567326b2b24431bca7dc9bfdd7b [file] [log] [blame]
/*-
* Copyright (c) 2015 Quantenna Communications, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $Id: ieee80211_qfdr.c 2759 2015-12-20 10:48:20Z Jason $
*/
#ifndef EXPORT_SYMTAB
#define EXPORT_SYMTAB
#endif
/*
* IEEE 802.11 sync scan result for Quantenna QFDR.
*/
#ifndef AUTOCONF_INCLUDED
#include <linux/config.h>
#endif
#include <linux/version.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/in.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>
#include <net/sock.h>
#include "net80211/if_media.h"
#include "net80211/ieee80211_var.h"
#define QFDR_REP_TIMEOUT (2 * HZ)
#define QFDR_REQ_MAX_SIZE 512
static char *local_ip = "0.0.0.0";
module_param(local_ip, charp, S_IRUGO);
MODULE_PARM_DESC(local_ip, "qfdr local ip");
static char *remote_ip = "0.0.0.0";
module_param(remote_ip, charp, S_IRUGO);
MODULE_PARM_DESC(remote_ip, "qfdr remote ip");
static unsigned short req_port = 0;
module_param(req_port, ushort, S_IRUGO);
MODULE_PARM_DESC(req_port, "qfdr port to recv req");
static unsigned short rep_port = 0;
module_param(rep_port, ushort, S_IRUGO);
MODULE_PARM_DESC(rep_port, "qfdr port to recv rep");
static struct socket *sock_send;
static struct socket *sock_recv_req;
static struct socket *sock_recv_rep;
static struct sockaddr_in sin_req;
static struct sockaddr_in sin_rep;
static struct task_struct *thread_recv_req;
static struct completion comp_recv_req_thread;
static struct socket * qfdr_create_recv_socket(unsigned int addr, unsigned short port)
{
struct sockaddr_in sin_bind;
struct socket *socket = NULL;
if (sock_create_kern(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &socket)) {
printk(KERN_ERR "%s: Failed to create socket\n", __func__);
return NULL;
}
sin_bind.sin_family = AF_INET;
sin_bind.sin_addr.s_addr = addr;
sin_bind.sin_port = htons(port);
if (kernel_bind(socket, (struct sockaddr *)&sin_bind, sizeof(sin_bind))) {
printk(KERN_ERR "%s: Failed to bind socket to port %pI4:%d\n", __func__, &addr, port);
sock_release(socket);
return NULL;
}
return socket;
}
static int qfdr_recv(struct socket *sock, char *buffer, size_t buflen)
{
struct msghdr msg = {NULL};
struct kvec iov;
int len;
/* adjust the RCVBUF */
if (buflen > sock->sk->sk_rcvbuf)
sock->sk->sk_rcvbuf = buflen;
iov.iov_base = buffer;
iov.iov_len = buflen;
len = kernel_recvmsg(sock, &msg, &iov, 1, buflen, 0);
return len;
}
static int qfdr_send(struct socket *sock, char *buffer, size_t buflen, struct sockaddr_in *dest_addr)
{
struct msghdr msg = {.msg_flags = MSG_DONTWAIT|MSG_NOSIGNAL};
struct kvec iov;
int len;
/* adjust the SNDBUF */
if (buflen > sock->sk->sk_sndbuf)
sock->sk->sk_sndbuf = buflen;
msg.msg_name = dest_addr;
msg.msg_namelen = sizeof(struct sockaddr_in);
iov.iov_base = buffer;
iov.iov_len = buflen;
len = kernel_sendmsg(sock, &msg, &iov, 1, buflen);
return len;
}
int qfdr_remote_giwscan(struct iwscanreq *req)
{
struct qfdr_remote_aplist_req request;
char *recvbuf;
size_t recvbuf_size;
int recvlen;
struct qfdr_remote_aplist_rep *rep;
request.type = QFDR_GIWSCAN;
memcpy(&request.info, req->info, sizeof(struct iw_request_info));
request.extra_len = req->end_buf - req->current_ev;
strcpy(request.dev_name, req->vap->iv_dev->name);
qfdr_send(sock_send, (char *)&request, sizeof(request), &sin_req);
recvbuf_size = request.extra_len + sizeof(struct qfdr_remote_aplist_rep);
recvbuf = kmalloc(recvbuf_size, GFP_KERNEL);
if (!recvbuf) {
printk(KERN_ERR "%s: Failed to alloc recvbuf\n", __func__);
return 0;
}
recvlen = qfdr_recv(sock_recv_rep, recvbuf, recvbuf_size);
if (recvlen < sizeof(struct qfdr_remote_aplist_rep)) {
kfree(recvbuf);
if (recvlen < 0)
printk(KERN_ERR "%s: Failed to recv rep with errno %d\n", __func__, recvlen);
else
printk(KERN_ERR "%s: recv invalid rep\n", __func__);
return 0;
}
rep = (struct qfdr_remote_aplist_rep *)recvbuf;
if (rep->res != ENOMEM && rep->type == QFDR_GIWSCAN) {
memcpy(req->current_ev, rep->extra, rep->length);
req->current_ev += rep->length;
}
kfree(recvbuf);
return rep->res;
}
int qfdr_remote_ap_scan_results(struct ap_scan_iter *iter)
{
struct qfdr_remote_aplist_req request;
char *recvbuf;
size_t recvbuf_size;
int recvlen;
struct qfdr_remote_aplist_rep *rep;
request.type = QFDR_AP_SCAN_RESULT;
request.extra_len = iter->end_buf - iter->current_env;
strcpy(request.dev_name, iter->vap->iv_dev->name);
qfdr_send(sock_send, (char *)&request, sizeof(request), &sin_req);
recvbuf_size = request.extra_len + sizeof(struct qfdr_remote_aplist_rep);
recvbuf = kmalloc(recvbuf_size, GFP_KERNEL);
if (!recvbuf) {
printk(KERN_ERR "%s: Failed to alloc recvbuf\n", __func__);
return 0;
}
recvlen = qfdr_recv(sock_recv_rep, recvbuf, recvbuf_size);
if (recvlen < sizeof(struct qfdr_remote_aplist_rep)) {
kfree(recvbuf);
if (recvlen < 0)
printk(KERN_ERR "%s: Failed to recv rep with errno %d\n", __func__, recvlen);
else
printk(KERN_ERR "%s: recv invalid rep\n", __func__);
return 0;
}
rep = (struct qfdr_remote_aplist_rep *)recvbuf;
if (rep->res != ENOMEM && rep->type == QFDR_AP_SCAN_RESULT) {
memcpy(iter->current_env, rep->extra, rep->length);
iter->current_env += rep->length;
iter->ap_counts += rep->ap_counts;
}
kfree(recvbuf);
return rep->res;
}
int qfdr_remote_siwscan(char *dev_name, struct iw_point *data)
{
struct qfdr_remote_scan_req *request;
int req_len = sizeof(struct qfdr_remote_scan_req);
if (data)
req_len += data->length;
if (req_len > QFDR_REQ_MAX_SIZE) {
printk(KERN_ERR "%s: qfdr peer canonly recv %d bytes req\n", __func__, QFDR_REQ_MAX_SIZE);
return -EINVAL;
}
request = kmalloc(req_len, GFP_KERNEL);
if (!request) {
printk(KERN_ERR "%s: Failed to alloc buf\n", __func__);
return -ENOMEM;
}
strcpy(request->dev_name, dev_name);
if (data) {
request->type = QFDR_SIWSCAN;
request->flags = data->flags;
request->length = data->length;
memcpy(request->pointer, data->pointer, data->length);
} else {
request->type = QFDR_SIWSCAN_SIMPLE;
}
qfdr_send(sock_send, (char *)request, req_len, &sin_req);
kfree(request);
return 0;
}
static void qfdr_process_req(char *recvbuf, int recvlen)
{
int type = *((int *)recvbuf);
struct qfdr_remote_aplist_rep rep_nomem = {ENOMEM, 0, 0, 0};
struct qfdr_remote_aplist_rep *rep;
if (type == QFDR_GIWSCAN) {
rep = qfdr_giwscan_for_remote((struct qfdr_remote_aplist_req *)recvbuf);
if (rep) {
qfdr_send(sock_send, (char *)rep, sizeof(struct qfdr_remote_aplist_rep) + rep->length, &sin_rep);
kfree(rep);
} else {
rep = &rep_nomem;
rep->type = QFDR_GIWSCAN;
qfdr_send(sock_send, (char *)rep, sizeof(struct qfdr_remote_aplist_rep) + rep->length, &sin_rep);
}
} else if (type == QFDR_AP_SCAN_RESULT) {
rep = qfdr_ap_scan_results_for_remote((struct qfdr_remote_aplist_req *)recvbuf);
if (rep) {
qfdr_send(sock_send, (char *)rep, sizeof(struct qfdr_remote_aplist_rep) + rep->length, &sin_rep);
kfree(rep);
} else {
rep = &rep_nomem;
rep->type = QFDR_AP_SCAN_RESULT;
qfdr_send(sock_send, (char *)rep, sizeof(struct qfdr_remote_aplist_rep) + rep->length, &sin_rep);
}
} else if (type == QFDR_SIWSCAN_SIMPLE || type == QFDR_SIWSCAN) {
qfdr_siwscan_for_remote((struct qfdr_remote_scan_req *)recvbuf);
}
}
static int qfdr_recv_req_thread(void *data)
{
char *recvbuf;
int recvlen;
recvbuf = kmalloc(QFDR_REQ_MAX_SIZE, GFP_KERNEL);
if (!recvbuf) {
printk(KERN_ERR "%s: Failed to alloc recvbuf\n", __func__);
return -ENOMEM;
}
allow_signal(SIGTERM);
while (!signal_pending(current)) {
recvlen = qfdr_recv(sock_recv_req, recvbuf, QFDR_REQ_MAX_SIZE);
if (recvlen > 0) {
qfdr_process_req(recvbuf, recvlen);
} else {
printk(KERN_WARNING "%s: broken pipe on socket\n", __func__);
}
}
kfree(recvbuf);
complete(&comp_recv_req_thread);
thread_recv_req = NULL;
return 0;
}
/*
* Module glue.
*/
MODULE_AUTHOR("Quantenna, Jason.Wang");
MODULE_DESCRIPTION("802.11 wireless support: Quantenna QFDR sync scan result");
#ifdef MODULE_LICENSE
MODULE_LICENSE("Dual BSD/GPL");
#endif
static int __init init_wlan_qfdr(void)
{
unsigned int local_addr, remote_addr;
local_addr = in_aton(local_ip);
if (local_addr == INADDR_ANY || local_addr == INADDR_NONE) {
printk(KERN_ERR "%s: Invalid local IP %pI4\n", __func__, &local_addr);
return -EINVAL;
}
remote_addr = in_aton(remote_ip);
if (remote_addr == INADDR_ANY || remote_addr == INADDR_NONE) {
printk(KERN_ERR "%s: Invalid remote IP %pI4\n", __func__, &remote_addr);
return -EINVAL;
}
if (req_port == 0) {
printk(KERN_ERR "%s: Invalid req port %u, must be greater than 0\n", __func__, req_port);
return -EINVAL;
}
if (rep_port == 0 || rep_port == req_port) {
printk(KERN_ERR "%s: Invalid rep port %u, must be greater than 0 and not same as req port\n", __func__, rep_port);
return -EINVAL;
}
if (sock_create_kern(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &sock_send)) {
printk(KERN_ERR "%s: Failed to create send socket\n", __func__);
goto fail;
}
sock_recv_req = qfdr_create_recv_socket(local_addr, req_port);
if (!sock_recv_req) {
printk(KERN_ERR "%s: Failed to create req recv socket\n", __func__);
goto fail;
}
sock_recv_rep = qfdr_create_recv_socket(local_addr, rep_port);
if (!sock_recv_rep) {
printk(KERN_ERR "%s: Failed to create rep recv socket\n", __func__);
goto fail;
}
/* set the RCVTIMEO */
sock_recv_rep->sk->sk_rcvtimeo = QFDR_REP_TIMEOUT;
sin_req.sin_family = AF_INET;
sin_req.sin_addr.s_addr = remote_addr;
sin_req.sin_port = htons(req_port);
sin_rep.sin_family = AF_INET;
sin_rep.sin_addr.s_addr = remote_addr;
sin_rep.sin_port = htons(rep_port);
thread_recv_req = kthread_run(qfdr_recv_req_thread, NULL, "qfdr");
if (IS_ERR(thread_recv_req)) {
printk(KERN_ERR "%s: Failed to start qfdr_recv_req_thread\n", __func__);
goto fail;
}
init_completion(&comp_recv_req_thread);
ieee80211_register_qfdr_remote_siwscan_hook(qfdr_remote_siwscan);
ieee80211_register_qfdr_remote_giwscan_hook(qfdr_remote_giwscan);
ieee80211_register_qfdr_remote_ap_scan_results_hook(qfdr_remote_ap_scan_results);
printk(KERN_INFO "Load qfdr module successfully, local ip:%pI4, remote ip:%pI4, req_port:%u, rep_port:%u\n", &local_addr, &remote_addr, req_port, rep_port);
return 0;
fail:
if (sock_send)
sock_release(sock_send);
if (sock_recv_req)
sock_release(sock_recv_req);
if (sock_recv_rep)
sock_release(sock_recv_rep);
return -EAGAIN;
}
module_init(init_wlan_qfdr);
static void __exit exit_wlan_qfdr(void)
{
ieee80211_register_qfdr_remote_siwscan_hook(NULL);
ieee80211_register_qfdr_remote_giwscan_hook(NULL);
ieee80211_register_qfdr_remote_ap_scan_results_hook(NULL);
if (thread_recv_req) {
send_sig(SIGTERM, thread_recv_req, 0);
wait_for_completion(&comp_recv_req_thread);
}
if (sock_send)
sock_release(sock_send);
if (sock_recv_req)
sock_release(sock_recv_req);
if (sock_recv_rep)
sock_release(sock_recv_rep);
}
module_exit(exit_wlan_qfdr);