You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
5.4 KiB
232 lines
5.4 KiB
/* 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");
|
|
|