blob: 8eb2251b1c070bec3a327c473fb57dae1dacd40c [file] [log] [blame]
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/rtnetlink.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <linux/msm_rmnet.h>
unsigned int multiplication_factor = 1;
module_param(multiplication_factor, uint, 0644);
struct net_device *mydevice;
static void iplo_do_ip_loopback(struct sk_buff *skb, int ip_offset)
{
struct iphdr *hdr;
struct icmphdr *icmp;
__be32 ipaddr;
int i;
hdr = (struct iphdr *)(skb->data + ip_offset);
ipaddr = hdr->saddr;
hdr->saddr = hdr->daddr;
hdr->daddr = ipaddr;
switch (hdr->protocol) {
case 1: /* ICMP */
icmp = (struct icmphdr *)(skb->data + ip_offset + hdr->ihl * 4);
if (icmp->type == ICMP_ECHO)
icmp->type = ICMP_ECHOREPLY;
break;
case 11: /* UDP */
break;
case 6: /* TCP */
break;
default:
break;
}
if (multiplication_factor < 2) {
netif_rx(skb);
skb->dev->stats.tx_packets++;
} else {
for (i = 0; i < (multiplication_factor - 1); i++) {
netif_rx(skb_copy(skb, GFP_ATOMIC));
skb->dev->stats.tx_packets++;
}
netif_rx(skb);
skb->dev->stats.tx_packets++;
}
}
static netdev_tx_t iplo_vnd_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
int ip_offset = 0;
switch (ntohs(skb->protocol)) {
case ETH_P_MAP:
ip_offset = 4;
case ETH_P_IP:
iplo_do_ip_loopback(skb, ip_offset);
dev->stats.rx_packets++;
break;
default:
dev->stats.tx_dropped++;
kfree_skb(skb);
break;
}
return NETDEV_TX_OK;
}
static int iplo_vnd_ioctl_extended(struct net_device *dev, struct ifreq *ifr)
{
struct rmnet_ioctl_extended_s ext_cmd;
int rc = 0;
rc = copy_from_user(&ext_cmd, ifr->ifr_ifru.ifru_data,
sizeof(struct rmnet_ioctl_extended_s));
if (rc) {
pr_err("%s() copy_from_user failed, error %d\n", __func__, rc);
return rc;
}
switch (ext_cmd.extended_ioctl) {
case RMNET_IOCTL_SET_MRU:
break;
case RMNET_IOCTL_GET_EPID:
ext_cmd.u.data = 100;
break;
case RMNET_IOCTL_GET_SUPPORTED_FEATURES:
ext_cmd.u.data = 0;
break;
case RMNET_IOCTL_GET_DRIVER_NAME:
strlcpy(ext_cmd.u.if_name, "rmnet_mhi",
sizeof(ext_cmd.u.if_name));
break;
default:
rc = -EINVAL;
break;
}
rc = copy_to_user(ifr->ifr_ifru.ifru_data, &ext_cmd,
sizeof(struct rmnet_ioctl_extended_s));
if (rc)
pr_err("%s() copy_to_user failed, error %d\n", __func__, rc);
return rc;
}
static int iplo_vnd_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
int rc = 0;
struct rmnet_ioctl_data_s ioctl_data;
switch (cmd) {
case RMNET_IOCTL_SET_LLP_IP: /* Set RAWIP protocol */
break;
case RMNET_IOCTL_GET_LLP: /* Get link protocol state */
ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data,
sizeof(struct rmnet_ioctl_data_s)))
rc = -EFAULT;
break;
case RMNET_IOCTL_GET_OPMODE: /* Get operation mode */
ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP;
if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data,
sizeof(struct rmnet_ioctl_data_s)))
rc = -EFAULT;
break;
case RMNET_IOCTL_SET_QOS_ENABLE:
rc = -EINVAL;
break;
case RMNET_IOCTL_SET_QOS_DISABLE:
rc = 0;
break;
case RMNET_IOCTL_OPEN:
case RMNET_IOCTL_CLOSE:
/* We just ignore them and return success */
rc = 0;
break;
case RMNET_IOCTL_EXTENDED:
rc = iplo_vnd_ioctl_extended(dev, ifr);
break;
default:
/* Don't fail any IOCTL right now */
rc = 0;
break;
}
return rc;
}
static int iplo_vnd_change_mtu(struct net_device *dev, int new_mtu)
{
if (0 > new_mtu || 16384 < new_mtu)
return -EINVAL;
dev->mtu = new_mtu;
return 0;
}
static const struct net_device_ops iplo_device_ops = {
.ndo_init = 0,
.ndo_do_ioctl = iplo_vnd_ioctl,
.ndo_start_xmit = iplo_vnd_start_xmit,
.ndo_change_mtu = iplo_vnd_change_mtu,
};
static void iplo_device_setup(struct net_device *dev)
{
dev->flags |= IFF_NOARP;
dev->netdev_ops = &iplo_device_ops;
dev->mtu = 1500;
dev->needed_headroom = 0;
dev->watchdog_timeo = 100;
dev->header_ops = 0;
dev->type = ARPHRD_RAWIP;
dev->hard_header_len = 0;
dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
dev->tx_queue_len = 1000;
}
int __init rmnet_iplo_init(void)
{
int rc;
pr_err("iplo: Module is coming up\n");
mydevice = alloc_netdev(100, "rmnet_mhi0", NET_NAME_ENUM,
iplo_device_setup);
if (!mydevice) {
pr_err("iplo: Failed to to allocate netdev for iplo\n");
return -EINVAL;
}
rtnl_lock();
rc = register_netdevice(mydevice);
rtnl_unlock();
if (rc != 0) {
pr_err("iplo: Failed to to register netdev [%s]\n",
mydevice->name);
free_netdev(mydevice);
return -EINVAL;
}
return 0;
}
void __exit rmnet_iplo_exit(void)
{
unregister_netdev(mydevice);
free_netdev(mydevice);
pr_err("iplo: Module is going away\n");
}
module_init(rmnet_iplo_init)
module_exit(rmnet_iplo_exit)
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("RmNet IP Loop Back Driver");